API Platform: GraphQL with Laravel
API Platform: GraphQL with Laravel
GraphQL is a powerful alternative to REST APIs that allows clients to request exactly the data they need. This lesson covers implementing GraphQL in Laravel using Lighthouse PHP, a framework for serving GraphQL from Laravel applications.
Installing and Configuring Lighthouse
Lighthouse is the most popular GraphQL library for Laravel. It provides a declarative way to define your GraphQL schema and integrates seamlessly with Eloquent.
# Install Lighthouse and GraphQL Playground
composer require nuwave/lighthouse
composer require mll-lab/laravel-graphql-playground
# Publish configuration
php artisan vendor:publish --tag=lighthouse-config
php artisan vendor:publish --tag=lighthouse-schema
# Configuration in config/lighthouse.php
return [
'route' => [
'uri' => '/graphql',
'middleware' => [
\Nuwave\Lighthouse\Support\Http\Middleware\AcceptJson::class,
],
],
'schema' => [
'register' => base_path('graphql/schema.graphql'),
],
];
# GraphQL Playground will be available at /graphql-playground
Defining Your GraphQL Schema
The schema is the contract between your client and server. It defines what queries and mutations are available and what data types they return.
# 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 {
"Get all users"
users: [User!]! @all
"Get a user by ID"
user(id: ID! @eq): User @find
"Search users by name"
searchUsers(name: String! @where(operator: "like")): [User!]! @all
"Get paginated posts"
posts(first: Int!, page: Int): PostPaginator @paginate
"Get current authenticated user"
me: User @auth
}
type User {
id: ID!
name: String!
email: String!
posts: [Post!]! @hasMany
created_at: DateTime!
updated_at: DateTime!
}
type Post {
id: ID!
title: String!
content: String!
author: User! @belongsTo
comments: [Comment!]! @hasMany
created_at: DateTime!
updated_at: DateTime!
}
type Comment {
id: ID!
body: String!
post: Post! @belongsTo
user: User! @belongsTo
created_at: DateTime!
}
type PostPaginator {
data: [Post!]!
paginatorInfo: PaginatorInfo!
}
type PaginatorInfo {
currentPage: Int!
lastPage: Int!
total: Int!
hasMorePages: Boolean!
}
Implementing Mutations
Mutations are GraphQL's way of modifying data. They're similar to POST, PUT, and DELETE in REST APIs.
# graphql/schema.graphql
type Mutation {
"Create a new user"
createUser(input: CreateUserInput! @spread): User @create
"Update an existing user"
updateUser(id: ID!, input: UpdateUserInput! @spread): User @update
"Delete a user"
deleteUser(id: ID! @whereKey): User @delete
"Create a post"
createPost(input: CreatePostInput!): Post @field(resolver: "PostMutator@create")
"Login user"
login(email: String!, password: String!): AuthPayload @field(resolver: "AuthMutator@login")
"Logout user"
logout: LogoutResponse @field(resolver: "AuthMutator@logout") @guard
}
input CreateUserInput {
name: String! @rules(apply: ["required", "string", "max:255"])
email: String! @rules(apply: ["required", "email", "unique:users,email"])
password: String! @rules(apply: ["required", "min:8"]) @hash
}
input UpdateUserInput {
name: String @rules(apply: ["string", "max:255"])
email: String @rules(apply: ["email", "unique:users,email"])
}
input CreatePostInput {
title: String! @rules(apply: ["required", "max:255"])
content: String! @rules(apply: ["required"])
author_id: ID! @rules(apply: ["required", "exists:users,id"])
}
type AuthPayload {
access_token: String!
token_type: String!
expires_in: Int!
user: User!
}
type LogoutResponse {
message: String!
status: String!
}
Creating Custom Resolvers
For complex logic, you'll need custom resolvers. These are PHP classes that handle specific queries or mutations.
<?php
// app/GraphQL/Mutations/PostMutator.php
namespace App\GraphQL\Mutations;
use App\Models\Post;
use Illuminate\Support\Facades\Auth;
class PostMutator
{
public function create($rootValue, array $args)
{
$user = Auth::user();
return Post::create([
'title' => $args['input']['title'],
'content' => $args['input']['content'],
'author_id' => $user->id,
'published_at' => now(),
]);
}
}
// app/GraphQL/Mutations/AuthMutator.php
namespace App\GraphQL\Mutations;
use Illuminate\Support\Facades\Auth;
use Illuminate\Validation\ValidationException;
class AuthMutator
{
public function login($rootValue, array $args)
{
$credentials = [
'email' => $args['email'],
'password' => $args['password'],
];
if (!Auth::attempt($credentials)) {
throw ValidationException::withMessages([
'email' => ['The provided credentials are incorrect.'],
]);
}
$user = Auth::user();
$token = $user->createToken('api-token')->plainTextToken;
return [
'access_token' => $token,
'token_type' => 'Bearer',
'expires_in' => 3600,
'user' => $user,
];
}
public function logout()
{
Auth::user()->currentAccessToken()->delete();
return [
'message' => 'Successfully logged out',
'status' => 'success',
];
}
}
// app/GraphQL/Queries/UserQuery.php
namespace App\GraphQL\Queries;
use App\Models\User;
class UserQuery
{
public function searchAdvanced($rootValue, array $args)
{
$query = User::query();
if (isset($args['name'])) {
$query->where('name', 'like', "%{$args['name']}%");
}
if (isset($args['email'])) {
$query->where('email', 'like', "%{$args['email']}%");
}
if (isset($args['created_after'])) {
$query->where('created_at', '>=', $args['created_after']);
}
return $query->get();
}
}
Subscriptions for Real-Time Data
GraphQL subscriptions enable real-time communication between client and server using WebSockets.
# Install Laravel WebSockets
composer require beyondcode/laravel-websockets
php artisan vendor:publish --provider="BeyondCode\LaravelWebSockets\WebSocketsServiceProvider"
php artisan migrate
# graphql/schema.graphql
type Subscription {
"Listen for new posts"
postCreated: Post
"Listen for comments on a specific post"
commentAdded(postId: ID!): Comment
"Listen for user notifications"
notificationSent: Notification @guard
}
type Notification {
id: ID!
type: String!
title: String!
message: String!
user: User!
read_at: DateTime
created_at: DateTime!
}
<?php
// Broadcasting a subscription event
use Nuwave\Lighthouse\Execution\Utils\Subscription;
// When a new post is created
$post = Post::create($data);
Subscription::broadcast('postCreated', $post);
// When a comment is added
$comment = Comment::create($data);
Subscription::broadcast('commentAdded', $comment, [
'postId' => $comment->post_id,
]);
// Filtering subscription data
// app/GraphQL/Subscriptions/CommentAdded.php
namespace App\GraphQL\Subscriptions;
use App\Models\Comment;
use Illuminate\Support\Facades\Auth;
class CommentAdded
{
public function filter($subscriber, $value)
{
// Only send to users who have access to the post
$comment = $value;
return $comment->post->isAccessibleBy(Auth::user());
}
public function resolve($rootValue, $args, $context)
{
return $rootValue;
}
}
Authorization in GraphQL
Implementing proper authorization is crucial for securing your GraphQL API.
# graphql/schema.graphql
type Query {
"Admin only query"
allUsers: [User!]! @all @guard @can(ability: "viewAny", model: "App\\Models\\User")
"Get posts user can access"
myPosts: [Post!]! @all @guard @inject(context: "user.id", name: "author_id")
}
type Mutation {
"Update post (author only)"
updatePost(id: ID!, input: UpdatePostInput!): Post
@update
@can(ability: "update", find: "id")
}
# Custom authorization in resolver
<?php
// app/Policies/PostPolicy.php
namespace App\Policies;
use App\Models\Post;
use App\Models\User;
class PostPolicy
{
public function update(User $user, Post $post)
{
return $user->id === $post->author_id;
}
public function delete(User $user, Post $post)
{
return $user->id === $post->author_id || $user->isAdmin();
}
}
// app/GraphQL/Mutations/PostMutator.php
public function delete($rootValue, array $args)
{
$post = Post::findOrFail($args['id']);
$this->authorize('delete', $post);
$post->delete();
return $post;
}
Exercise 1: Blog GraphQL API
Create a complete GraphQL API for a blog system with the following features:
- Queries: List posts with pagination, search posts by title/content, get post with comments
- Mutations: Create/update/delete posts, add comments to posts, like/unlike posts
- Implement authentication using Sanctum tokens
- Add authorization to ensure only authors can edit their posts
- Create a subscription for new comments on posts
Exercise 2: E-Commerce Product API
Build a GraphQL API for an e-commerce platform:
- Define types for Products, Categories, Orders, and Users
- Implement queries for product search with filtering (price range, category, rating)
- Create mutations for cart management (add to cart, update quantity, checkout)
- Add real-time inventory updates using subscriptions
- Implement field-level authorization (hide prices for guests)
Exercise 3: Social Network GraphQL
Develop a social network GraphQL API with:
- User profiles with posts, followers, and following relationships
- News feed query that returns posts from followed users
- Mutations for follow/unfollow, create posts, like/comment
- Real-time notifications subscription
- Implement cursor-based pagination for infinite scrolling
- Add rate limiting to prevent spam