Setup GraphQL in Laravel using Lighthouse

So, you're eager to level up your API game with GraphQL for Laravel? Awesome! In this article, i'm going to help you set up a GraphQL...

Karan Datwani
Karan Datwani
Share:

So, you're eager to level up your API game with GraphQL for Laravel? Awesome! In this article, i'm going to help you set up a GraphQL server using Lighthouse. If you aren't very familiar with GraphQL, i recommend our previous article, about How GraphQL is better than REST APIs.

Key Concepts

Before i jump into the Laravel setup, here are some quick GraphQL terms you might want to get familiar with:

  1. Schema: The schema defines the capabilities of the API. It shows what requests are possible and how they'll execute.
  2. Directives: Functions that let us set instructions, how to fetch or push the data.
  3. Query: Requests for reading data.
  4. Mutation: Requests to add or update data.
  5. Subscription: Long-lived connections for real-time data.

Enter Lighthouse: Your GraphQL Superhero!

Now, let's talk about Lighthouse! 🦸‍♂️ It makes GraphQL server set up super easy for the Laravel projects. It lets us quickly set up schema, directives, query, mutation, subscriptions, everything! So you can focus on coding fantastic features, not boilerplate.

Installation

Step 1: Install Lighthouse:

composer require nuwave/lighthouse

Step 2: Publish the default schema:

php artisan vendor:publish --tag=lighthouse-schema

Step 3: Publish the config file:

php artisan vendor:publish --tag=lighthouse-config
# will publish to config/lighthouse.php

Let's install a GUI too?

If GraphQL had a best friend, it would be GraphiQL. It's an in-browser tool that lets you write and test your GraphQL queries to make our development smoother. It's like Postman but tailored for GraphQL. With its auto-complete feature and real-time error highlighting, it's a must-have in your GraphQL toolkit.

Let's spice things up with a GUI, shall we? Check out this wonderful package:

  1. Installation: In your terminal, type:
composer require mll-lab/laravel-graphiql
  1. Publish the config:
php artisan vendor:publish --tag=graphiql-config
  1. Run and Enjoy: Start your Laravel server and head to http://localhost:8000/graphiql. And bam! You can start querying! You're ready to explore your GraphQL endpoints in style.

Few configurations I followed to set the upcoming example.

Let's consider a simple blog application with Users and Posts for this tutorial. To quicly set an example:

  • I used Laravel starter kit to get the register & login form, which comes ready with it.
  • I created User and Post models and defined following relationships between them:
//app/Models/User.php
public function posts(){
    return $this->hasMany(Post::class);
}
//app/Models/Post.php
public function user(){
    return $this->belongsTo(User::class);
}

Need your attention here: This example will also cover authentication. I've used Laravel Sanctum for authentication. Under Sanctum, i prefered "Stateful" authentication for APIs. It works in the same old way using session & cookies, without taking hassle of storing & securing access_tokens on web browsers.🤝

For this:

  • I've added the required middleware and guard in config/lighthouse.php to make sanctum work for lighthouse too. You can find them here;
  • I've followed two more steps listed here, to make GraphiQL send required header for session authentication;
  • Set APP_URL perfectly with the port no and address. Example:APP_URL=http://localhost:8000;

Done! Enough with boring setup!

Let's code our GraphQL APIs.🚀

Define Schema: Define what can be retrieved or pushed: what data GraphQL APIs should expose, the Return types, how they’ll execute is defined in schema files.

You can find the default schema file at graphql/schema.graphql to code the schema:

#Define Return type & attributes
type User {
    id: ID!
    name: String!
    email: String!
    posts: [Post!] @hasMany
}

type Post {
    id: ID!
    title: String!
    content: String!
    user: User! @belongsTo
}

# Queries to fetch records
type Query {
    posts: [Post!] @paginate(defaultCount: 10)
    post(id: ID! @whereKey): Post @find

    me: User @auth
    user(id: ID! @whereKey): User @find
}

# Mutation to manipulate records
type Mutation {
    createPost(title: String!, content: String!): Post
    deletePost(id: ID! @whereKey): Post @delete
}

I tell you a surprise here, our GraphQL CRUD APIs are ready with the above snippet.

Notice, the above schema has directives like @paginate, @find, @auth,@delete which covered everything. No Controller, NO CODE required. Everything is already covered by Lighthouse😎.

What about custom code?

In the above automation, i had a custom requirement too! I created a custom resolver for createPost mutation which creates posts under the authenticaed user:

//app/GraphQL/Mutations/CreatePost.php
namespace App\GraphQL\Mutations;

use App\Models\Post;
use Illuminate\Support\Facades\Auth;

class CreatePost
{
    public function __invoke($rootValue, array $args)
    {
        $user = Auth::user();

        $post = $user->posts()->create([
            'title' => $args['title'],
            'content' => $args['content'],
        ]);

        return $post;
    }
}

Here, the createPost mutation automatically invokes our custom resolver app/GraphQL/Mutations/CreatePost.php . By default, the lighthouse looks for resolvers in the studly case of your field name. They look in the registered namespace that is defined in your lighthouse’s config file.

Let's understand Queries

Queries:

Queries are where we define what data to fetch. In our graphql/schema.graphql, i added two example queries:

type Query {
  # Query to list all entries
  posts: [Post!] @paginate(defaultCount: 10)
  # Query to fetch a specific post by id
  post(id: ID!): Post @find
}

Example 1 - Query to list all entries

Here, we defined query posts which would return an array of 10 posts per page using pagination.

Let's fetch all posts with their author's name by sending following query using GraphiQL:

query{
  posts(page:1) {
    data {
      title
      content
      user {
        name
      }
    }
    paginatorInfo {
      count
      currentPage
      lastPage
    }
  }
}

And we'll get following json response:

{
  "data": {
    "posts": {      
      "data": [
        {
          "title": "GraphQL 101",
          "content": "Introduction to GraphQL...",
          "user": {
            "name": "Karan"
          }
        },
        ...
      ],
     "paginatorInfo": {
         "count": 15,
         "currentPage": 1,
         "lastPage": 2
      }
    }
  }
}

Notice:

  • It returns only those attribues which we asked in json.
  • It also return relationship, the user.

Example 2 - Query to fetch a specific post

The query post(id: ID!): Post @find takes an input ID to find and return Post object.

Let's query a post using GraphiQL by sending:

query{
  post(id: 1) {    
    title
    user{
      name
    }
  }
}

It returns asked attributes with a relationship, the user. No need for another request to get that!

Response:

{
  "data": {
    "post": {
      "title": "GraphQL 101",
      "user": {
        "name": "Karan"
      }
    }
  }
}

How to get authenticated user with his posts?

We created two queries to fetch user:

type Query {
  # query to authenticate user and return `User` object
  me: User @auth
    
  # query to find by id and return `User` object
  user(id: ID! @whereKey): User @find
}

The following would get you authenticaed user with his posts. The relation works because User type has an attribute posts: [Post!] @hasMany defined in the schema.

query{
  me {
    id
    name
    email
    posts {
      id
      title
      content
    }
  }
}

Response:

{
  "data": {
    "me": {
      "id": "1",
      "name": "Karan Datwani",
      "email": "[email protected]",
      "posts": [
        {
          "id": "3",
          "title": "New Post Title",
          "content": "This is the content of the new post."
        },        
      ]
    }
  }
}

The last query in the schema user(id: ID! @whereKey): User @find allows to fetch user by id. You can try it by requesting:

query{
  user(id: 1) {
    id
    name
    email
    posts {
      id
      title
      content
    }
  }
}

Let's move to Mutations

Think of Mutations as functions you define in your Schema. You call them to modify data, eg. create a post or delete a post.

Create a post:

I needed a custom code while creating post. For that, i created a custom resolver CreatePost.php for this mutation createPost. No directives used here:

type Mutation {
  # Take title and content as input and return the Post object
  createPost(title: String!, content: String!): Post    
}

Send the following request to above mutation to create a post:

mutation {
  createPost(title: "Laravel GraphQL Advanced", content: "Deep dive into GraphQL...") {
    id
    title
    content
  }
}

Response:

{
  "data": {
    "createPost": {
      "id": "7",
      "title": "Laravel GraphQL Advanced",
      "content": "Deep dive into GraphQL..."
    }
  }
}

Delete a post:

If you have read this far, I'm sure delete doesn't need an explaination. You can call it with:

mutation {
  deletePost(id: 1) {
    id
  }
}

What if schema file gets too big?

Yes, you can break your schema in multiple files & import them with a comment in the main schema. For example, add #import models/*.graphql into the main schema to import all other defined .graphql schema files from subfolder graphql/models.

Available Directives

Lighthouse offers a variety of directives to simplify your GraphQL server setup. Here's a list of some of the available them:

  1. @create: Handles the creation of a new model instance.
  2. @update: Handles the update of an existing model instance.
  3. @delete: Handles the deletion of a model instance.
  4. @upsert: Handles the creation or update of a model instance based on provided conditions.
  5. @find: Retrieves a single model by its primary key.
  6. @all: Fetches a list of all model instances.
  7. @paginate: Returns a paginated list of model instances.
  8. @belongsToMany: Resolves a field as a connection to a many-to-many relation.
  9. @hasOne: Resolves a field as a connection to a one-to-one relation.
  10. @hasMany: Resolves a field as a connection to a one-to-many relation.
  11. @belongsTo: Resolves a field as a connection to an inverse one-to-one or many-to-one relation.
  12. @search: Adds full-text search capabilities to a field.
  13. @orderBy: Orders a list of results by one or multiple fields.
  14. @count: Returns the count of related models.
  15. @groupCount: Returns the count of related models, grouped by a certain field.
  16. @date: Parses and formats date fields.
  17. @rename: Renames a field in the schema.
  18. @guard: Protects routes by checking user authentication.
  19. @can: Checks user authorization using Laravel's built-in policies.
  20. @deprecated: Marks a field or type as deprecated.
  21. @broadcast: Broadcasts subscriptions through Laravel's broadcasting system.
  22. @rules: Applies validation rules to input values.
  23. @rulesApply: Applies validation rules to a list of input values.
  24. @trim: Trims string input.
  25. @spread: Spreads input values to the surrounding argument or field.
  26. @eq: Filters a model list by checking equality of a field.
  27. @neq: Filters a model list by checking non-equality of a field.
  28. @json: Handles JSON columns in a database.

Find latest available directives here

A Sneak Peek into Apollo Client

Before we wrap up, let's take a brief moment to talk about Apollo Client. It's a library that helps to integrate Web/Android/IOS App with GraphQL endpoint. But, we'll dive deep into this topic in another article. So, stay tuned!

Conclusion: Embrace the Future of APIs!

Congratulations, you've unlocked the power of GraphQL with Lighthouse! 🎉 Say goodbye to over-fetching and under-fetching data, and embrace the future of APIs. GraphQL is all about simplicity, flexibility, and performance. It's a perfect match for API developers.

So, the next time you're building APIs in Laravel, consider giving GraphQL a shot. Trust me, you won't look back! Happy coding! 😊

Want to receive more articles like this?

Subscribe to our "Article Digest". We'll send you a list of the new articles, every week, month or quarter - your choice.

Reactions & Comments

What do you think about this?

Latest Articles

Wondering what our community has been up to?