GICT knowledge base
How to Build a GraphQL API with Laravel
Published
GraphQL is a query language and runtime for APIs that was developed by Facebook in 2012 and open-sourced in 2015. Unlike traditional REST APIs where you have multiple endpoints for different resources, GraphQL provides a single endpoint that can handle all your data requests.
Key Benefits of GraphQL:
Precise Data Fetching:
Instead of getting all user data, you can request only the name and email:
{
user(id: 1) {
name
email
}
}
Single Request for Related Data:
Get a user with their posts in one request:
{
user(id: 1) {
name
posts {
title
content
}
}
}
Strong Type System: Your API schema defines exactly what data is available and what types it should be.
Self-Documenting: GraphQL APIs come with built-in documentation and introspection.
GraphQL vs REST
| REST | GraphQL |
|---|---|
| Multiple endpoints (/users, /posts) | Single endpoint (/graphql) |
| Fixed data structure | Flexible data fetching |
| Over-fetching/under-fetching issues | Get exactly what you need |
| Multiple requests for related data | Single request for complex data |
Why Laravel + GraphQL?
Laravel’s robust ecosystem combined with GraphQL’s flexibility creates a powerful API development experience. Laravel provides excellent database handling, authentication, and validation, while GraphQL offers precise data fetching and excellent developer tooling.
Let’s build a practical blog API to see how these technologies work together.
What You’ll Build
We’ll create a simple blog API with two main resources:
- Users – who can write articles
- Articles – blog posts with authors
Setting Up Laravel Project
First, create a new Laravel project:
composer create-project laravel/laravel graphql-blog cd graphql-blog
Configure your database in .env:
DB_CONNECTION=sqlite DB_DATABASE=database/database.sqlite
Create the SQLite database:
touch database/database.sqlite
Database Models and Migrations
Update User Migration
Edit the existing user migration to add an API token:
// database/migrations/create_users_table.php
Schema::create('users', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('email')->unique();
$table->timestamp('email_verified_at')->nullable();
$table->string('password');
$table->string('api_token', 80)->unique()->nullable();
$table->rememberToken();
$table->timestamps();
});
Create Article Model and Migration
php artisan make:model Article -m
Update the Article migration:
// database/migrations/create_articles_table.php
Schema::create('articles', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained()->onDelete('cascade');
$table->string('title');
$table->text('content');
$table->timestamps();
});
Define Model Relationships
Update your models:
// app/Models/User.php
class User extends Authenticatable
{
use HasApiTokens, HasFactory, Notifiable;
protected $fillable = ['name', 'email', 'password'];
protected $hidden = ['password', 'remember_token'];
public function articles()
{
return $this->hasMany(Article::class);
}
}
// app/Models/Article.php
class Article extends Model
{
use HasFactory;
protected $fillable = ['title', 'content', 'user_id'];
public function user()
{
return $this->belongsTo(User::class);
}
}
Run Migrations and Seed Data
php artisan migrate
Create some test data using Tinker:
php artisan tinker
// In Tinker
$user = User::factory()->create([
'name' => 'John Doe',
'email' => '[email protected]',
'password' => bcrypt('password')
]);
Article::factory()->create([
'title' => 'My First Article',
'content' => 'This is the content of my first article.',
'user_id' => $user->id
]);
Installing Lighthouse GraphQL
Install Lighthouse package:
composer require nuwave/lighthouse
Publish the configuration:
php artisan vendor:publish --tag=lighthouse-config
Create the GraphQL schema directory:
mkdir graphql
Creating GraphQL Schema
Create your GraphQL schema file:
# graphql/schema.graphql
"A datetime string with format `Y-m-d H:i:s`"
scalar DateTime @scalar(class: "Nuwave\\Lighthouse\\Schema\\Types\\Scalars\\DateTime")
type Query {
# User queries
users: [User!]! @paginate
user(id: ID! @eq): User @find
me: User @auth(guard: "api")
# Article queries
articles: [Article!]! @paginate
article(id: ID! @eq): Article @find
}
type Mutation {
# Authentication
login(email: String!, password: String!): AuthPayload
@field(resolver: "AuthResolver@login")
# User operations
createUser(
name: String!
email: String! @rules(apply: ["email", "unique:users"])
password: String! @rules(apply: ["min:6"]) @bcrypt
): User @create
# Article operations
createArticle(
title: String!
content: String!
): Article @field(resolver: "ArticleResolver@create") @guard(with: ["api"])
updateArticle(
id: ID!
title: String
content: String
): Article @field(resolver: "ArticleResolver@update") @guard(with: ["api"])
deleteArticle(id: ID!): Article @delete @guard(with: ["api"])
}
type User {
id: ID!
name: String!
email: String!
articles: [Article!]! @hasMany
created_at: DateTime!
updated_at: DateTime!
}
type Article {
id: ID!
title: String!
content: String!
user: User! @belongsTo
created_at: DateTime!
updated_at: DateTime!
}
type AuthPayload {
token: String!
user: User!
}
Implementing Queries
The basic queries are handled automatically by Lighthouse directives:
- @all – fetches all records
- @paginate – adds pagination
- @find – finds a single record
- @eq – adds where clause
Test your queries by starting the server:
php artisan serve
Visit http://localhost:8000/graphql-playground and try:
query {
users {
data {
id
name
email
articles {
id
title
}
}
}
}
Building Mutations
Create Authentication Resolver
php artisan make:class GraphQL/Resolvers/AuthResolver
<?php
// app/GraphQL/Resolvers/AuthResolver.php
namespace App\GraphQL\Resolvers;
use App\Models\User;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;
use GraphQL\Error\Error;
class AuthResolver
{
public function login($root, array $args)
{
$user = User::where('email', $args['email'])->first();
if (!$user || !Hash::check($args['password'], $user->password)) {
throw new Error('Invalid credentials');
}
$token = Str::random(60);
$user->api_token = $token;
$user->save();
return [
'token' => $token,
'user' => $user
];
}
}
Create Article Resolver
php artisan make:class GraphQL/Resolvers/ArticleResolver
<?php
// app/GraphQL/Resolvers/ArticleResolver.php
namespace App\GraphQL\Resolvers;
use App\Models\Article;
use Illuminate\Support\Facades\Auth;
use GraphQL\Error\Error;
class ArticleResolver
{
public function create($root, array $args)
{
$user = Auth::user();
$article = new Article($args);
$article->user_id = $user->id;
$article->save();
return $article;
}
public function update($root, array $args)
{
$article = Article::findOrFail($args['id']);
// Check if user owns the article
if ($article->user_id !== Auth::id()) {
throw new Error('Unauthorized');
}
$article->update(array_filter($args, fn($value) => $value !== null));
return $article;
}
}
Adding Authentication
Update your API configuration in config/auth.php:
'guards' => [
'api' => [
'driver' => 'token',
'provider' => 'users',
'hash' => false,
],
],
Testing Your API
1. Create a User
mutation {
createUser(
name: "Jane Doe"
email: "[email protected]"
password: "password123"
) {
id
name
email
}
}
2. Login
mutation {
login(email: "[email protected]", password: "password123") {
token
user {
id
name
}
}
}
3. Create Article (with Authorization)
Add the token to headers in GraphQL Playground:
{
"Authorization": "Bearer YOUR_TOKEN_HERE"
}
Then create an article:
mutation {
createArticle(
title: "My GraphQL Journey"
content: "Learning GraphQL with Laravel has been amazing!"
) {
id
title
user {
name
}
}
}
4. Query Articles with Relationships
query {
articles(first: 5) {
data {
id
title
content
user {
name
email
}
}
paginatorInfo {
total
hasMorePages
}
}
}
5. Get Current User
query {
me {
id
name
email
articles {
id
title
created_at
}
}
}
Production Tips
Performance Optimization
1. Query Complexity Analysis
Prevent expensive queries by limiting complexity:
// config/lighthouse.php
'security' => [
'max_query_complexity' => 1000,
'max_query_depth' => 15,
'disable_introspection' => env('LIGHTHOUSE_DISABLE_INTROSPECTION', false),
]
2. Caching
Cache frequently requested data:
// In your resolver
use Illuminate\Support\Facades\Cache;
public function popularArticles()
{
return Cache::remember('popular_articles', 3600, function () {
return Article::with('user')->orderBy('views', 'desc')->take(10)->get();
});
}
3. DataLoader Pattern
Lighthouse automatically handles N+1 query problems, but you can optimize further:
type Query {
articles: [Article!]! @paginate
@with(relation: "user") # Eager load users
}
Security Best Practices
1. Disable Introspection in Production
LIGHTHOUSE_DISABLE_INTROSPECTION=true
2. Rate Limiting
Add rate limiting to your GraphQL endpoint:
// routes/web.php
Route::middleware(['throttle:60,1'])->group(function () {
Route::post('/graphql', [\Nuwave\Lighthouse\Support\Http\Controllers\GraphQLController::class, 'query']);
});
3. Input Validation
Always validate inputs in your schema:
createUser(
email: String! @rules(apply: ["email", "unique:users"])
password: String! @rules(apply: ["min:8", "confirmed"])
): User
Monitoring and Debugging
1. Query Logging
Enable query logging for debugging:
// AppServiceProvider.php
use Illuminate\Support\Facades\DB;
public function boot()
{
if (app()->environment('local')) {
DB::listen(function ($query) {
Log::info('Query: ' . $query->sql . ' [' . implode(', ', $query->bindings) . ']');
});
}
}
2. GraphQL Playground
Use GraphQL Playground for development and testing:
# Available at: http://localhost:8000/graphql-playground
Conclusion
Building a GraphQL API with Laravel offers significant advantages over traditional REST APIs. You get precise data fetching, strong typing, excellent developer tools, and Laravel’s robust ecosystem all in one package.
Key Benefits You’ve Achieved:
- Efficient Data Fetching: Clients request only the data they need
- Single Endpoint: No more managing multiple REST endpoints
- Type Safety: GraphQL schema ensures consistent data structure
- Great Developer Experience: Built-in documentation and query playground
- Laravel Integration: Seamless use of Eloquent models and relationships
What You’ve Built:
- Complete CRUD operations for articles
- User authentication with API tokens
- Database relationships between users and articles
- Input validation and authorization
- Pagination for large datasets
- Production-ready security features
This foundation can easily be extended with features like real-time subscriptions, file uploads, advanced search, and more complex business logic.
GraphQL with Laravel provides a modern, efficient approach to API development that scales well from small projects to enterprise applications.
Related guides
Handling PATCH and PUT Requests with Next.js and Laravel
Learn to handle PATCH and PUT requests between Next.js and Laravel.
LaravelHow to Get Environment Variables in Laravel React
Learn how to get environment variables in Laravel React apps. Simple guide covering MIX_, VITE_ prefixes and Laravel backend methods with code examples.
LaravelLaravel 12 Global Scope Guide – Easy Query Filtering
Learn how to use Laravel 12’s new global scope features to secure, simplify, and streamline database queries across your entire application.
LaravelLaravel Debugbar: Ultimate Debug Tool for Faster Development
Stop hunting through scattered log files! Learn journalctl to quickly find what went wrong on your Linux server and fix issues faster.
