Blog Software Testing

Unleashing Flexibility and Efficiency: Building a GraphQL API with Laravel

Deepak M Deepak M | Last updated: August 20, 2024 |

GraphQL is a query language for APIs and can be used to query data from a server while also allowing clients to specify exactly what data is needed.

Project Overview

Before we get started, we’ll need to get familiar with the project we are attempting to build. To do that, we will define our resources and create our GraphQL schema, which we will later use to serve our API.

Project Resources 

Our application will consist of two resources: Clients and Users. These resources will be defined as object types in our GraphQL schema example

type Query {

  users: [User!]!

  clients: [Client!]!

  user(id: ID!): User

  client(id: ID!): Client

}

type User {

  id: ID!

  name: String!

  email: String!

}

type Client {

  id: ID!

  name: String!

  email: String!

}

Looking at the schema, we can see that we have a one-to-many relationship between our two objects.

Setting up our Laravel Project 

Now that we have defined our GraphQL schema, let’s get our Laravel project up and running. Let’s start by creating a new Laravel via Composer project:

$ composer create-project –prefer-dist laravel/laravel sample-graphql 

Just to make sure we have everything working

$ cd sample-graphql

$ php artisan serve

Laravel development server started: <http://127.0.0.1:8000>

Database Models and Migrations

To accomplish this task, we will utilize MySQL. Please update the database setup in the default .env file accordingly. Additionally, manually create a database in your phpMyAdmin interface.”

DB_CONNECTION=mysql

DB_HOST=127.0.0.1

DB_PORT=3306

DB_DATABASE=db_graphql

DB_USERNAME=XXXX

DB_PASSWORD=XXXX

For this functionality, we require two tables: the `users` table and the `clients` table. The `users` table already exists in Laravel by default, so we only need to create the `clients` table. Let’s create this table using the `make:migration` command.

$ php artisan make:migration create_clients_table

Now, we need to create models for both the `users` and `clients` tables. However, the model for the `users` table already exists in the Laravel framework.

For Client:

$ php artisan make:model Client

Here’s how you can create models for Clients and their migration files, using Laravel’s Artisan command line.

Now, let’s make the necessary adjustments in the generated migration file 

In the migration file, insert the code highlighted in blue below.

/database/migrations/XXX_XX_XX_XXXX_create_clients_table.php 

use Illuminate\Database\Migrations\Migration;

use Illuminate\Database\Schema\Blueprint;

use Illuminate\Support\Facades\Schema;

return new class extends Migration

{

    /**

     * Run the migrations.

     */

    public function up(): void

    {

//domainname/name – new fields

        Schema::create(‘clients’, function (Blueprint $table) {

            $table->bigIncrements(‘id’);

            $table->string(‘name’);

           $table->string(‘domainname’)->unique(); // Assuming domain names are unique

           $table->ipAddress(‘ipaddress’); // For storing IP addresses

            $table->string(‘city’);

            $table->string(‘country’);

            $table->string(‘state’);

            $table->string(‘zipcode’);

            $table->timestamps(); // This will create the `created_at` and `updated_at` columns

        });

    }

    public function down()

    {

        Schema::dropIfExists(‘clients’);

    }

};

Modify the existing users migration file

database/migrations/XXX_XX_XX_XXXX_create_users_table.php

For User 

use Illuminate\Database\Migrations\Migration;

use Illuminate\Database\Schema\Blueprint;

use Illuminate\Support\Facades\Schema;

return new class extends Migration

{

    /**

     * Run the migrations.

     */

    public function up(): void

    {

        Schema::create(‘users’, function (Blueprint $table) {

            $table->bigIncrements(‘id’);

            $table->unsignedBigInteger(‘client_id’); // Assuming users belong to a client

            $table->string(‘firstname’);

            $table->string(‘lastname’);

            $table->string(‘password’);

            $table->string(’email’)->unique(); // Assuming emails are unique

            $table->string(‘status’); // e.g., active, inactive, etc.

            $table->timestamps(); // This will create the `created_at` and `updated_at` columns

            // Foreign key constraint (ensure the clients’ table is migrated first)

            $table->foreign(‘client_id’)->references(‘id’)->on(‘clients’)->onDelete(‘cascade’);

        });

    }

    public function down()

    {

        Schema::dropIfExists(‘users’);

    }

};

Now that we have our migration files defined, let’s proceed to execute them against our database:

Please make sure to migrate the clients table before the users table to prevent any foreign key constraint issues.

To begin, execute the client migration file. You can use the terminal command provided in the example below. Simply copy the name of the client file and paste it into the command prompt:

php artisan migrate –path=/database/migrations/

xxxx_xx_xx_xxxxxx_create_clients_table.php

This will migrate the clients table. Once completed, you can proceed with migrating the users table in a similar manner.

$ php artisan migrate

Next, lets update models by deafening the necessary adjustments 

For Client model 

app/Models/Client.php

namespace App\Models;

use App\Models\User;

use Illuminate\Foundation\Auth\User;

use Illuminate\Database\Eloquent\Factories\HasFactory;

use Illuminate\Database\Eloquent\Model;

class Client extends Model

{

    use HasFactory;

     // Assuming your clients table has standard Laravel naming conventions

     protected $fillable = [

        ‘name’, ‘domainname’, ‘ipaddress’, ‘city’, ‘country’, ‘state’, ‘zipcode’,

    ];

    /**

     * Get the users for the client.

     *

     * @return \Illuminate\Database\Eloquent\Relations\HasMany

     */

    public function users()

    {

        return $this->hasMany(User::class);

    }

}

Remember to import the User class in the Client.php file. 

For User Model 

app/Models/User.php

namespace App\Models;

use App\Models\Client;

use Illuminate\Database\Eloquent\Model;

use Illuminate\Notifications\Notifiable;

use Illuminate\Database\Eloquent\Factories\HasFactory;

class Users extends Model

{

    use HasFactory;

    use Notifiable;

    protected $fillable = [

        ‘client_id’, ‘firstname’, ‘lastname’, ‘username’, ‘password’, ’email’, ‘status’,

    ];

    /**

     * The attributes that should be hidden for arrays.

     *

     * @var array

     */

    protected $hidden = [

        ‘password’, ‘remember_token’,

    ];

    /**

     * User belongs to a client.

     *

     * @return \Illuminate\Database\Eloquent\Relations\BelongsTo

     */

    public function client()

    {

        return $this->belongsTo(Client::class);

    }

}

Remember to import the Client file in the User.php file.

Database Seeding 

Now that we have our models and migrations set up, let’s seed our database. We’ll start by creating some seeder classes for our clients and users tables:

$ php artisan make:seeder ClientsTableSeeder

$ php artisan make:seeder UsersTableSeeder

Next, let’s set them up to insert some dummy data into our MySQL database:

File Path – database/seeds/UsersTableSeeder.php

namespace Database\Seeders;

use App\User;

use App\Models\User;

use App\Models\Client;

use Illuminate\Database\Seeder;

use Illuminate\Database\Console\Seeds\WithoutModelEvents;

class UsersTableSeeder extends Seeder

{

    /**

     * Run the database seeds.

     */

    public function run(): void

    {

    // If not, you need to seed the clients’ table first or create a client here before assigning.    

        User::truncate();

        $faker = \Faker\Factory::create();

        $password = bcrypt(‘secret’);

        $status = ‘active’; // Assuming ‘active’ is a valid status for your users

        $clientIds = Client::pluck(‘id’); // Efficiently fetch all client IDs

        User::create([

            ‘client_id’ => $clientIds->random(), // Randomly select a client ID,

            ‘firstname’ => $faker->firstName,

            ‘lastname’  => $faker->lastName,

            ’email’     => ‘graphql@test.com’,

            ‘password’  => $password,

            ‘status’    => $status,

        ]);

        for ($i = 0; $i < 10; ++$i) {

            User::create([

               ‘client_id’ => $clientIds->random(), // Randomly select a client ID

                ‘firstname’ => $faker->firstName,

                ‘lastname’  => $faker->lastName,

               ’email’     => $faker->unique()->safeEmail, // Generate unique emails each time

                ‘password’  => $password,

                ‘status’    => $status,

            ]);

        }

    }

}

Filepath – database/seeds/ClientsTableSeeder.php

namespace Database\Seeders;

use App\Models\Client;

use Illuminate\Database\Seeder;

use Illuminate\Support\Facades\DB;

use Illuminate\Database\Console\Seeds\WithoutModelEvents;

class ClientsTableSeeder extends Seeder

{

    /**

     * Run the database seeds.

     */

    public function run(): void

    {

        DB::statement(‘SET FOREIGN_KEY_CHECKS=0;’);

       // If using Laravel 8 or later, don’t forget to update the namespace if necessary

       \App\Models\Client::truncate();

       \App\Models\Client::unguard();

       DB::statement(‘SET FOREIGN_KEY_CHECKS=1;’);

       $faker = \Faker\Factory::create();

        // Reset auto-increment value

        DB::statement(‘ALTER TABLE clients AUTO_INCREMENT = 1;’);

       // Creating 10 dummy client records as an example

       for ($i = 0; $i < 10; $i++) {

           Client::create([

               ‘name’       => $faker->company,

               ‘domainname’ => $faker->domainName,

               ‘ipaddress’  => $faker->ipv4,

               ‘city’       => $faker->city,

               ‘country’    => $faker->country,

               ‘state’      => $faker->state,

               ‘zipcode’    => $faker->postcode,

           ]);

       }

   }

}

/database/seeds/DatabaseSeeder.php

use Illuminate\Database\Seeder;

use Database\Seeders\UsersTableSeeder;

use Database\Seeders\ClientsTableSeeder;

class DatabaseSeeder extends Seeder

{

    /**

     * Seed the application’s database.

     *

     * @return void

     */

    public function run()

    {

        // Order matters here if your users are related to clients

        // Ensure clients are seeded first so that users can be assigned to them

        $this->call(ClientsTableSeeder::class);

        $this->call(UsersTableSeeder::class);

    }

}

Finally, let’s go ahead and run our database seeders to get some data into our database:

$ php artisan db:seed

That’s it you have successfully seeded your MySql Database!.

Laravel Lighthouse and GraphQL Server

Now that we have our database and models set up, it’s time to start building out our GraphQL server. Currently, there are several solutions available for Laravel, but for this article, we’re going to use Lighthouse. It allows developers to quickly set up a GraphQL server using Laravel with little boilerplate while also being flexible enough to allow developers to customize it to fit the needs of just about any project. 

$ composer require nuwave/lighthouse:^5.0

Next, let’s publish the Lighthouse Configuration file:

$ php artisan vendor:publish –tag=lighthouse-config

So let’s go ahead and create our schema file and set up our user object type and query:

$ mkdir graphql

From your preferred development directory, create a directory for a new project and cd into it:

$ cd graphql/ 

Node Setup – Difference between npm/nvm

npm serves as a package manager designed to handle JavaScript packages and dependencies. On the other hand, nvm functions as a version manager specifically tailored for the installation and management of multiple Node.js versions on a single machine. NVM Installation process

This allows you to switch between different Node.js versions easily. This should be more than 14

$ nvm install 16

$ nvm use 16

Initialize a new Node.js project with npm (or another package manager you prefer, such as Yarn):

$ npm init –yes

Your project directory now contains a package.json file.

Step 2: Install Dependencies 

  • apollo-server is the core library for Apollo Server itself, which helps you define the shape of your data and how to fetch it.
  • GraphQL is the library used to build a GraphQL schema and execute queries against it.

Run the following command to install both of these dependencies and save them in your project’s node_modules directory:

$ npm install apollo-server graphql

You’ll need to install apollo-server, graphql, and mysql2. If you haven’t installed them yet, you can do so by running. This package is used to connect you MySql database. 

$ npm install apollo-server graphql mysql2

Also create an empty index.js file in your project’s directory:

$ touch index.js

To keep things simple, index.js will contain all of the code for this example application.

Step 3: Define your GraphQL schema

Every GraphQL server (including Apollo Server) uses a schema to define the structure of data that clients can query. In this example, we’ll create a server for querying a collection of users and clients.

Open index.js in your preferred editor and paste the following into it:

const { ApolloServer, gql } = require(“apollo-server”);

const mysql = require(“mysql2/promise”);

const dbConfig = {

    host: “localhost”,

    user: “XXXX”, // database username*

    database: “db_graphql”, // database name*

    password: “XXXX”, // database password* 

};

const initDbConnection = async () => {

    return await mysql.createConnection(dbConfig);

};

// Defining our schema

const typeDefs = gql`

    # A “Client” type that defines the queryable fields for every client in our data source.

    # A “User” type that defines the queryable fields for every user in our data source.

    # The “Query” type is special: it lists all of the available queries that

    # clients can execute, along with the return type for each. In this

    # case, the “users” query returns an array of zero or more Users, and

    # If we need to get some data from db we can use like this

    type User {

        id: ID!

        firstname: String

        lastname: String

        email: String

        status: String

        client_id: ID

        password: String

    }

    # below the query is for fetching data from the database

    type Query {

        getUsers: [User]

    }

`;

// Step 4: Define a Resolver

// Resolvers define the technique for fetching the types defined in the

// schema. This resolver retrieves users from the “users” array above.

const resolvers = {

    Query: {

        getUsers: async () => {

            try {

                const connection = await initDbConnection();

                const [rows] = await connection.execute(“SELECT * FROM users”);

                return rows.map((row) => ({

                    id: row.id,

                    firstname: row.firstname,

                    lastname: row.lastname,

                    email: row.email,

                    status: row.status,

                    client_id: row.client_id,

                    // Ensure all fields required by your GraphQL schema are mapped here

                }));

            } catch (error) {

                console.error(“Error fetching users:”, error);

                // Handle errors appropriately; you might want to throw an error or return an empty array

                throw new Error(“Failed to fetch users”);

            }

            // console.log(“Users data: “, rows);

        },

    },

};

// Step 5: Create an instance of Apollo Server

// Creating the Apollo Server with our type definitions, resolvers, and telling it

// to use the playground (useful for testing and exploring your API)

const server = new ApolloServer({ typeDefs, resolvers });

// Starting the server

server.listen().then(({ url }) => {

    console.log(`🚀 Server ready at ${url}`);

});

Step 6: Start your Server

$ node index.js

You should see the following output:

🚀 Server ready at http://localhost:4000/

GraphQL API with Laravel

Please refer to the code in the Git repository below for your reference

git clone https://github.com/Deepakdckap/Laravel_Graphql_index.js.git

When starting up Playground, click on the “URL Endpoint” tab, and type in http://localhost:4000/graphql to point GraphQL Playground to our server. On the left side of the editor, we can query for our data, so let’s start by asking for all the users that we seeded the database with:

query Query {

 getUsers {

   id

   firstname

   lastname

   email

 }

}

When you hit the play button in the middle of the IDE (or click Ctrl+Enter), you’ll see the JSON output of our server on the right side, which will look example like this:

 “data”: {

   “getUsers”: [

     {

       “id”: “1”,

       “firstname”: “Hildegard”,

       “lastname”: “Pagac”,

       “email”: “graphql@test.com”

     },

     {

       “id”: “2”,

       “firstname”: “Taya”,

       “lastname”: “Stracke”,

       “email”: “premnath@gmail.com”

     },

   ]

 }

Note: Because we used Faker to seed our database, the data so the fields will be different.

GraphQL Mutation:

Now that we can query our data, let’s create some mutations to create some new users and with the existing client_id

In your existing typeDefs function do the following mutation definition: 

 input userInput {

        id: ID!

        firstname: String

        lastname: String

        email: String

        password: String

        status: String

        client_id: ID

    }

#Mutation

type Mutation {

        loginUser(userInput: userInput): User

    }

Then, ensure you have `bcrypt` installed in your project. If not, you can install it using npm or yarn:

$ npm install bcrypt

const bcrypt = require(‘bcrypt’);

const saltRounds = 10; // The cost factor controls how much time is needed to calculate a single bcrypt hash. The higher the cost factor, the more hashing rounds are done.

In your index.js existing resolvers function the following mutation (after the Query function)

Mutation: {

    loginUser: async (_, { userInput }) => {

        const connection = await initDbConnection();

        // Hash the password before storing it in the database

        const hashedPassword = await bcrypt.hash(userInput.password, saltRounds);

        const query = `INSERT INTO users (id, firstname, lastname, password, email, client_id, status, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, NOW(), NOW())`;

        // Use the hashedPassword instead of the plain password

        const values = [

            userInput.id,

            userInput.firstname,

            userInput.lastname,

            hashedPassword, // Use the hashed password here

            userInput.email,

            userInput.client_id,

            userInput.status,

        ];

        // Execute the query with the values

        await connection.execute(query, values);

       // console.log(“User data inserted successfully.”);

        // Don’t forget to close the connection when done

        connection.end();

    },

},

In the GraphQL Playground, click on the “Root” tab on the left side, then select “mutation.” Follow the next steps as directed.

Root -> Mutation -> loginUser -> Arguments -> userInput

Now that we have our createUser mutation field set up, let’s go ahead and run it in GraphQL Playground with the following: below example

mutation Mutation($userInput: userInput) {

 loginUser(userInput: $userInput) {

   client_id,

   id,

   firstname,

   lastname,

   email,

   status,

   password

 }

}

Assign the values for the required variables, here is an example below

{

 “userInput”: {

“id”: 12, #auto increment id

“firstname”: “john”,

“lastname”: “bekam”,

“email”: johnbekam@gmail.com,

“password”: “johnbekam@143”, #password will be encrypted while inserting

   “client_id”: 7, #existing client_id in the database

“status”: “active”

 }

}

GraphQL API with Laravel

GraphQL API with Laravel

Record will be inserted successfully. Thank you for taking the time to read my blog!

Leave a Reply