nuwave/lighthouse
Lighthouse is a Laravel-first GraphQL server framework. Define your schema, wire resolvers, and handle common tasks like validation, auth, pagination, and Eloquent integration, with flexibility for custom GraphQL needs.
## Getting Started
### Minimal Setup
1. **Installation**:
```bash
composer require nuwave/lighthouse
php artisan lighthouse:install
This generates a graphql route and config file (config/lighthouse.php).
First Query:
Define a basic schema in graphql/schema.graphql:
type Query {
hello: String
}
Create a resolver in app/GraphQL/Queries/Hello.php:
namespace App\GraphQL\Queries;
class Hello
{
public function __invoke($root, array $args)
{
return 'World';
}
}
Test via GraphQL Playground (accessible at /graphql) with:
query {
hello
}
Key Files:
graphql/schema.graphql: Schema definition (SDL).app/GraphQL/: Resolvers (auto-discovered via naming conventions).config/lighthouse.php: Configuration (e.g., middleware, directives).Naming Conventions:
app/GraphQL/Queries/{PascalCaseName}.phpapp/GraphQL/Mutations/{PascalCaseName}.phpapp/GraphQL/Subscriptions/{PascalCaseName}.php
Example:// app/GraphQL/Queries/GetUser.php
class GetUser {
public function __invoke($root, array $args) {
return User::find($args['id']);
}
}
Resolver Arguments:
Use the $resolveInfo parameter for advanced use cases (e.g., client directives, field selection):
public function __invoke($root, array $args, $context, \Nuwave\Lighthouse\Support\Contracts\ResolveInfo $resolveInfo) {
$clientDirective = new \Nuwave\Lighthouse\ClientDirectives\ClientDirective('example');
$arguments = $clientDirective->forField($resolveInfo);
// ...
}
Modular Schema:
Use #import in schema.graphql to split definitions across files:
#import "types/user.graphql"
#import "queries/posts.graphql"
Load dynamically via SchemaStitcher:
$stitcher = new \Nuwave\Lighthouse\Schema\Source\SchemaStitcher(__DIR__.'/graphql');
return $stitcher->getSchemaString();
Dynamic Schema:
Listen to BuildSchemaString event to inject schema parts at runtime:
event(new \Nuwave\Lighthouse\Events\BuildSchemaString($schemaString));
@auth: Protect fields/queries:
type Query {
secret: String @auth
}
Configure in config/lighthouse.php:
'directives' => [
'auth' => \Nuwave\Lighthouse\Directives\AuthDirective::class,
],
@deprecated: Mark schema elements as deprecated:
type Query {
oldField: String @deprecated(reason: "Use newField instead")
}
Detect usage via:
DetectDeprecatedUsage::handle(function (array $deprecations) {
// Log or notify
});
scalar Upload @scalar(class: "Nuwave\\Lighthouse\\Schema\\Types\\Scalars\\Upload")
type Mutation {
uploadFile(file: Upload!): String
}
public function __invoke($root, array $args) {
/** @var \Illuminate\Http\UploadedFile $file */
$file = $args['file'];
return $file->store('uploads');
}
multipart/form-data with GraphQL multipart spec (e.g., Apollo Upload Client).Batch Loading:
Use loadMany in resolvers for N+1 queries:
public function __invoke($root, array $args) {
return User::whereIn('id', $args['ids'])->get();
}
Configure in config/lighthouse.php:
'batch_loading' => true,
Caching: Cache resolvers or schema fragments:
$cache = app(\Nuwave\Lighthouse\Support\Cache\Cache::class);
$cache->remember('schema_fragment', 60, function () {
return $this->buildSchemaFragment();
});
Resolver Signature Mismatch:
null for missing data (not false or exceptions).public function __invoke($root, array $args) {
return User::find($args['id']); // Returns null if not found
}
Circular Dependencies:
TypeA references TypeB, which references TypeA).Client Directives:
@skip/@include are client-side only. Server-side logic must handle multiple directive configurations per field (see docs).File Uploads:
X-Requested-With: XMLHttpRequest header is set if using CSRF middleware.multipart/form-data and the GraphQL multipart spec.Schema Validation:
php artisan lighthouse:schema:validate to check for issues.Enable Debug Mode:
'debug' => env('APP_DEBUG', false),
storage/logs/lighthouse.log.Query Introspection:
query {
__schema {
types {
name
fields {
name
type {
name
}
}
}
}
}
Resolver Logging:
\Log::debug('Resolver args:', $args);
Schema Diffing:
php artisan lighthouse:schema:diff.Custom Directives:
class CustomDirective extends \Nuwave\Lighthouse\Directives\BaseDirective {
protected $config = [
'auth' => \Nuwave\Lighthouse\Directives\AuthDirective::class,
'custom' => \App\GraphQL\Directives\CustomDirective::class,
];
}
Middleware:
\Nuwave\Lighthouse\Transport\Http\Middleware\ValidateGraphQLRequest::class,
\App\Http\Middleware\CheckUserRole::class,
Type System:
$scalar = new \Nuwave\Lighthouse\Schema\Types\CustomScalarType('DateTime', function ($value) {
return \Carbon\Carbon::parse($value);
});
$typeRegistry->mustBeRegistered($scalar);
Event Listeners:
AfterResolveType, BeforeExecuteQuery):
event(new \Nuwave\Lighthouse\Events\AfterResolveType($type, $result));
CORS:
'cors' => [
'paths' => ['graphql', 'graphql/*'],
'allowed_methods' => ['*'],
'allowed_origins' => ['*'],
'allowed_headers' => ['*'],
'exposed_headers' => [],
'max_age' => 0,
'supports_credentials' => false,
],
Depth Limit:
10):
'query_depth_limit' => 15,
Complexity Analysis:
'complexity' => [
'enabled' => true,
'limit'
How can I help you explore Laravel packages today?