Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Lighthouse Laravel Package

nuwave/lighthouse

Lighthouse is a GraphQL framework for Laravel that combines the best ideas of both ecosystems. It handles common tasks with ease and offers flexibility when you need it, letting you serve a GraphQL API directly from your Laravel app.

View on GitHub
Deep Wiki
Context7

Getting Started

Lighthouse is schema-first: you write GraphQL SDL, attach directives, and PHP classes fill in the gaps. The mental model shift from "controllers + routes" to "schema + directives" is the main onboarding cost, so start by reading the SDL, not the PHP.

Minimal setup:

composer require nuwave/lighthouse
php artisan vendor:publish --provider="Nuwave\Lighthouse\LighthouseServiceProvider" --tag=config
php artisan vendor:publish --provider="Nuwave\Lighthouse\LighthouseServiceProvider" --tag=schema
php artisan lighthouse:validate-schema

Where to look first, in order:

  1. graphql/schema.graphql — the root file; #import pulls in the rest of your SDL.
  2. config/lighthouse.php — route, middleware, guards, namespaces for generated classes, schema registration globs.
  3. app/GraphQL/ — where scaffolding commands put resolvers, directives, scalars.
  4. https://lighthouse-php.com — the docs are directive-reference-shaped and genuinely good; each directive page has a runnable example.

A first use case that demonstrates the value in ~10 lines: expose an existing Eloquent model as a paginated, filterable query without writing a resolver.

type Query {
  posts(search: String @like, orderBy: _ @orderBy(columns: ["created_at"])): [Post!]! @paginate(type: CONNECTION)
}

type Post @node {
  id: ID!
  title: String!
  author: User! @belongsTo
}

Then php artisan lighthouse:ide-helper to get _lighthouse_ide_helper.php with @property annotations for your model classes — it makes the directive magic far less opaque in the IDE.

Scaffold the pieces you'll need custom: lighthouse:query, lighthouse:mutation, lighthouse:directive, lighthouse:validator, lighthouse:scalar, lighthouse:subscription, lighthouse:field-middleware. Also check the README's migration notice: the repo is moving to spawnia/lighthouse, so pin expectations on the Composer package name and follow that discussion before a major upgrade.

Implementation Patterns

Default to built-in model directives; drop to resolvers only when logic is genuinely irregular. @all, @paginate, @find, @first, @hasMany, @belongsTo, @hasOneThrough cover most read paths and get Eloquent optimizations for free (including shortcut foreign-key selection when clients select only an ID).

Queries. Compose filtering/sorting/pagination declaratively rather than in PHP:

posts(filter: _ @filter, where: _ @whereConditions(columns: ["status", "author_id"]),
      orderBy: _ @orderBy): [Post!]! @paginate

Use @scope(name: "published") to expose model scopes, and @withoutGlobalScopes when a tenant or soft-delete scope must be bypassed for a specific field.

Custom root resolvers are single-action invokables (__invoke) registered by naming convention or @field(resolver:). Keep them thin — they should compose services, not contain query building. The RootResolverSignatureRector rule (v6.69+) can auto-fix __invoke signatures when upgrading.

Mutations. @create, @update, @upsert, @delete handle typical CRUD. Two patterns carry most of the weight:

  • Nested mutations with @nest for saving a parent plus relations in one operation. Since v6.65, @upsert/@upsertMany accept explicit identifying columns, which is the clean way to avoid duplicate-row surprises on nested writes.
  • Wrap multi-step writes in @transaction.

Authorization and validation are schema-level concerns. @guard(with: ["api"]) at field or type level, @can/@canFind/@canModel for policy checks, @validator(class: "App\GraphQL\Validators\CreatePostValidator") for FormRequest-style rule classes. Since v6.48, @bind gives you Route Model Binding semantics — resolving and 404-ing before the mutation body runs.

Custom directives come in three flavors, and picking the right one matters:

  • FieldMiddleware — wrap/extend resolution (logging, caching, transformation).
  • ArgResolver — transform a single argument into a model or query constraint. If it mutates state, implement SaveAwareArgResolver (v6.68+) so pre/post-save timing is honored inside @nest.
  • RootResolver — an entirely alternate resolution strategy for a field.

Generate with lighthouse:directive --type=field-middleware etc. to get the stub plus the @api docblocks.

Performance patterns that pay off immediately: enable the schema cache in production, add the file-based query cache (v6.63) so OPcache absorbs parsed queries, turn on Automatic Persisted Queries for mobile clients, and use @cache plus @lazyLoad for expensive relations — the latter composes with @paginate since v6.60. Expose query complexity via @complexity and read the score from the BuildExtensionsResponse lifecycle event for rate limiting.

Subscriptions work with Redis broadcasting and, since v6.47, Laravel Reverb; encrypted channels are supported (v6.53). Treat them as a separate deployment concern (broadcaster, queue worker) rather than something you bolt on late.

Testing. Use Nuwave\Lighthouse\Testing\MakesGraphQLRequests and MocksResolvers (PHPUnit 12 supported since v6.64). Schema cache refreshes lazily on test requests, so you rarely need to clear it manually.

$this->graphQL('{ posts { data { id } } }')->assertJsonPath('data.posts.data', []);

Feature-flagging the schema itself with @feature, @show, @hide (extendable to types, arguments, input types since v6.46) lets you ship a schema that varies by environment or tenant without branching in resolvers.

Gotchas and Tips

Schema cache is the number-one source of "my change didn't apply." Deploy scripts must run php artisan lighthouse:clear-schema-cache (the old lighthouse:clear-cache is deprecated). Run lighthouse:validate-schema in CI — it catches SDL merge conflicts, directive misplacement, and missing type references before runtime.

Only @api-annotated code is stable across minor versions. Everything else in Lighthouse is internal and may change in a patch release. If you extend DirectiveLocator, ASTCache, or similar, expect breakage; prefer the documented extension points (custom directives, middleware, lifecycle events). ASTCache::path() and ArgumentSet::exists() were made public precisely because people kept reaching into internals.

N+1 queries are easy to create accidentally. @hasMany on a list of posts without @with or @lazyLoad will hammer the DB. Enable query logging in dev and check relations specifically; the shortcut foreign-key optimization only applies when you select just an ID, and it silently doesn't fire if the client includes __typename in an unexpected shape (fixed in v6.51, but a good reminder that field selection changes resolver behavior).

Nested mutation semantics are subtle and have shifted. create on a HasOne that already exists now throws (v6.64.3), upsert without an ID updates rather than creates, and pre-save arg resolvers are lifted out of @nest and all run (v6.69.2). Write integration tests around nested writes, not just unit tests on the resolver.

Null vs. empty string vs. absent. @convertEmptyStringsToNull is your friend for optional inputs, and paginated queries now tolerate an explicit null page argument (v6.64.1) — behavior that used to be inconsistent.

Error surfaces changed. @can* directives now expose the original resolver errors instead of wrapping them, and client-safe error reporting is configurable (v6.49). Audit your error shaping after upgrading; clients parsing extensions.category may see different shapes.

Config globs drive schema composition. config('lighthouse.schema.register') autoloads files by glob — file order matters for extend type and duplicate definitions. Keep a single root schema and #import explicitly rather than relying on glob discovery order for anything meaningful.

@paginate type selection is a schema decision, not a resolver detail. PAGINATOR (page/perPage), SIMPLE (first/after), and CONNECTION (Relay cursors) produce completely different client contracts. Changing it later is a breaking API change; pick based on the client's expectations up front.

IDE helper is a build artifact. _lighthouse_ide_helper.php should be generated in CI/dev and gitignored, or you'll get merge conflicts on every regeneration. Recent versions also format it more cleanly (v6.43.1).

Watch the repository transition. The move to spawnia/lighthouse will affect issue links, docs URLs, and possibly the Composer package name. Pin your version constraint to a specific minor for production and read UPGRADE.md before each major — minor upgrades promise no PHP or SDL changes, which is the contract worth relying on.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
thuraaung2493/laravel-rulecraft
cyberwizard/schedule-catchup
livetennisapi/livetennisapi-laravel
roundly-consulting/notifications-for-laravel
flightsadmin/bootstrap-starter-kit
lasserafn/laravel-intempus
likyly/sdk
tdt/input
coreation/php-shapefile
coreation/gisconverter
primix/widgets
primix/notifications
primix/actions
primix/tables
primix/details
primix/forms
primix/support
marcelgwerder/laravel-api-handler
firebrikdotcom/conveyor-laravel-broadcaster
kanata-php/conveyor-laravel-broadcaster