GraphQL no Laravel com Lighthouse
1. Introdução ao GraphQL e ao Lighthouse
GraphQL é uma linguagem de consulta para APIs que permite aos clientes solicitar exatamente os dados que precisam, eliminando problemas comuns do REST como over-fetching e under-fetching. Enquanto no REST você geralmente recebe estruturas fixas de dados, no GraphQL o cliente especifica os campos desejados em cada requisição.
O Lighthouse é um pacote PHP que traz GraphQL para Laravel de forma elegante. Ele adota uma abordagem schema-first, onde você primeiro define o esquema GraphQL em arquivos .graphql e depois conecta esses tipos aos modelos Eloquent. As principais vantagens incluem:
- Integração nativa com Eloquent e suas relações
- Geração automática de queries e mutations baseadas em diretivas
- Suporte a validação Laravel, autenticação e autorização
- Performance otimizada com prevenção do problema N+1
2. Instalação e Configuração Inicial
Para instalar o Lighthouse, execute:
composer require nuwave/lighthouse
Em seguida, publique os assets:
php artisan vendor:publish --provider="Nuwave\Lighthouse\LighthouseServiceProvider"
Isso criará o diretório graphql/ na raiz do projeto com a seguinte estrutura:
graphql/
schema.graphql # Schema principal
directives/ # Diretivas personalizadas
enums/ # Definições de enums
interfaces/ # Definições de interfaces
O arquivo de configuração config/lighthouse.php permite ajustar diversos aspectos:
<?php
return [
'route' => [
'prefix' => 'graphql',
'middleware' => ['api']
],
'schema' => [
'register' => base_path('graphql/schema.graphql'),
],
'cache' => [
'enable' => env('LIGHTHOUSE_CACHE_ENABLE', false),
],
];
3. Definindo o Schema GraphQL
Vamos criar um schema básico para usuários e posts. Edite o arquivo graphql/schema.graphql:
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!
user: User! @belongsTo
comments: [Comment!]! @hasMany
created_at: DateTime!
updated_at: DateTime!
}
type Comment {
id: ID!
body: String!
post: Post! @belongsTo
user: User! @belongsTo
}
type Query {
users: [User!]! @all
user(id: ID! @eq): User @find
posts: [Post!]! @paginate
}
type Mutation {
createUser(name: String!, email: String!, password: String!): User! @create
updateUser(id: ID!, name: String, email: String): User! @update
deleteUser(id: ID!): User! @delete
}
As diretivas @hasMany e @belongsTo conectam automaticamente os relacionamentos Eloquent. O Lighthouse infere os nomes dos métodos nos modelos.
4. Queries: Consultando Dados
Com o schema acima, queries comuns funcionam imediatamente:
# Buscar todos os usuários com seus posts
query {
users {
id
name
posts {
title
content
}
}
}
# Buscar um usuário específico
query {
user(id: 1) {
name
email
}
}
# Paginação de posts
query {
posts(first: 10, page: 1) {
data {
title
user {
name
}
}
paginatorInfo {
total
currentPage
lastPage
}
}
}
Para filtros e ordenação, adicione diretivas ao schema:
type Query {
users(name: String @where(operator: "like")): [User!]! @all
posts(orderBy: _ @orderBy(column: "created_at" direction: DESC)): [Post!]! @paginate
}
Isso permite consultas como:
query {
users(name: "João") {
id
name
}
posts(orderBy: [{ column: CREATED_AT, order: DESC }]) {
data {
title
created_at
}
}
}
5. Mutations: Criando, Atualizando e Deletando
As mutations automáticas já cobrem operações CRUD básicas. Para adicionar validação, use a diretiva @rules:
type Mutation {
createPost(
title: String! @rules(apply: ["required", "min:3", "max:255"])
content: String! @rules(apply: ["required", "min:10"])
user_id: ID! @rules(apply: ["exists:users,id"])
): Post! @create
}
Para mutations personalizadas com lógica de negócio, crie uma classe resolver:
<?php
namespace App\GraphQL\Mutations;
use App\Models\Post;
use Illuminate\Support\Facades\Validator;
class CreatePostWithValidation
{
public function __invoke($rootValue, array $args): Post
{
$validator = Validator::make($args, [
'title' => 'required|unique:posts|max:255',
'content' => 'required',
'user_id' => 'required|exists:users,id'
]);
if ($validator->fails()) {
throw new \Exception($validator->errors()->first());
}
return Post::create([
'title' => $args['title'],
'content' => $args['content'],
'user_id' => $args['user_id']
]);
}
}
E registre no schema:
type Mutation {
createPostWithValidation(
title: String!
content: String!
user_id: ID!
): Post! @field(resolver: "App\\GraphQL\\Mutations\\CreatePostWithValidation")
}
6. Autenticação e Autorização
Para proteger campos com autenticação, use a diretiva @guard:
type Mutation {
createPost(
title: String!
content: String!
): Post! @create @guard
}
type Query {
myPosts: [Post!]! @all @guard
}
Para autorização baseada em políticas Laravel, use @can:
type Mutation {
updatePost(id: ID!, title: String, content: String): Post! @update @can(ability: "update", find: "id")
deletePost(id: ID!): Post! @delete @can(ability: "delete", find: "id")
}
Configure o middleware de autenticação no arquivo config/lighthouse.php:
'middleware' => [
\Nuwave\Lighthouse\Http\Middleware\AcceptJson::class,
\Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
'auth:sanctum',
],
7. Otimização e Boas Práticas
Para evitar o problema N+1, use as diretivas @with e @hasMany:
type Query {
users: [User!]! @all @with(relation: "posts")
}
Para paginação eficiente, utilize @paginate com @first:
type Query {
recentPosts: [Post!]! @paginate @orderBy(column: "created_at", direction: DESC)
latestPost: Post @first @orderBy(column: "created_at", direction: DESC)
}
Testes com PHPUnit usando helpers do Lighthouse:
<?php
namespace Tests\Feature\GraphQL;
use Tests\TestCase;
use Nuwave\Lighthouse\Testing\MakesGraphQLRequests;
class PostTest extends TestCase
{
use MakesGraphQLRequests;
public function test_can_create_post()
{
$response = $this->graphQL('
mutation {
createPost(
title: "Test Post"
content: "This is a test post content"
user_id: 1
) {
id
title
}
}
');
$response->assertJson([
'data' => [
'createPost' => [
'title' => 'Test Post'
]
]
]);
}
public function test_requires_authentication()
{
$this->graphQL('
mutation {
createPost(
title: "Unauthorized"
content: "Should fail"
user_id: 1
) {
id
}
}
')->assertGraphQLErrorMessage('Unauthenticated.');
}
}
8. Deploy e Considerações Finais
Em produção, ative o cache de schema no .env:
LIGHTHOUSE_CACHE_ENABLE=true
Para versionamento, use namespaces no schema:
type Query {
v1: V1Queries! @namespace(field: "App\\GraphQL\\Queries\\V1")
v2: V2Queries! @namespace(field: "App\\GraphQL\\Queries\\V2")
}
Integre com ferramentas como GraphQL Playground ou Altair para testar sua API durante o desenvolvimento:
// config/lighthouse.php
'graphiql' => [
'enabled' => env('GRAPHQL_PLAYGROUND_ENABLED', true),
],
O Lighthouse oferece uma experiência robusta para construir APIs GraphQL no Laravel. Sua abordagem schema-first, combinada com a integração profunda com Eloquent e as ferramentas de autenticação do Laravel, torna o desenvolvimento produtivo e o código limpo. Comece com tipos simples e vá adicionando complexidade gradualmente conforme sua aplicação cresce.
Referências
- Documentação oficial do Lighthouse — Guia completo do pacote com exemplos de schema, diretivas e configurações
- Laravel GraphQL: Introdução ao Lighthouse — Artigo do Laravel News sobre primeiros passos com GraphQL no Laravel
- GraphQL no Laravel com Lighthouse - Tutorial — Tutorial prático cobrindo queries, mutations e autenticação
- Lighthouse: Autenticação e Autorização — Documentação oficial sobre proteção de endpoints com Sanctum e Passport
- Testando GraphQL com Lighthouse e PHPUnit — Guia oficial para escrever testes de queries e mutations
- Prevenção do Problema N+1 no GraphQL — Estratégias de otimização com diretivas
@withe@hasMany - GraphQL Playground Integration — Repositório oficial para integração com GraphQL Playground no Laravel