Laravel GraphQL核心是选对包(rebing轻量渐进式,lighthouse schema-first自动化强但学习成本高)和写对resolver(严格匹配类型、防N+1、清缓存生效、支持动态查询参数及字段权限控制)。

直接上手 Laravel GraphQL,核心就两点:选对包、写对 resolver。用 rebing/graphql-laravel 或 nuwave/lighthouse 都能跑通,但默认不带类型推断、不自动绑定 Eloquent 关系、不处理字段权限——这些得自己补。
怎么选 GraphQL 包:rebing vs lighthouse
rebing/graphql-laravel 更轻,适合已有 REST 架构想渐进式加 GraphQL 查询能力的项目;nuwave/lighthouse 是 schema-first,靠 SDL(Schema Definition Language)文件驱动,自动映射模型、分页、授权,但学习成本高、调试时堆栈深。
- 如果你只想要「按需取字段 + 一次请求查多层关联」,
rebing配合手动写的UserType和UsersQuery就够用 - 如果团队愿意写
schema.graphql文件、接受 PHP 类型和 SDL 类型双向同步,lighthouse的@all、@field、@can指令能省大量样板代码 -
lighthouse默认开启 query batching 和 persisted queries,rebing得自己配中间件或改GraphQLController
resolver 怎么写才不踩坑
resolver 不是万能胶水,它必须返回与 type 定义完全匹配的结构。常见错误是返回 Eloquent Collection 却没在 fields 里声明 type => Type::listOf(...),或者嵌套关系用了 with() 但 resolver 没调 ->load() 导致 N+1。
- 查询单个用户时,resolver 返回
User::find($args['id']),但 type 中id字段写了Type::nonNull(Type::int()),而数据库里 id 是字符串(如 UUID),就会报"Expected value of type \"Int\" but got: \"string\"" - 要查用户及其订单,别在 resolver 里写
$user->orders,得用$user->load('orders')或提前在 query 参数里加with: ['orders']并由统一中间件注入 - resolver 函数签名里的
$root多数时候是 null(根查询),别误当成父对象传参用;真正链式嵌套靠的是字段级 resolver,不是顶层函数
schema 修改后为什么前端查不到新字段
GraphQL schema 不是写完就生效的。Lighthouse 缓存 SDL 到 storage/framework/lighthouse/schema.stamp,rebing 默认把 schema 编译成 PHP 类存在 bootstrap/cache/graphql.php。改了 *Type.php 或 schema.graphql 后,不清理缓存就等于没改。
- Lighthouse:运行
php artisan lighthouse:clear-cache,再php artisan lighthouse:ide-helper(可选,补全提示) - Rebing:删掉
bootstrap/cache/graphql.php,并确保APP_DEBUG=true,否则它不会重生成 - 所有包都依赖
cache:clear,但仅清 Laravel 缓存不够,必须单独清 GraphQL 层缓存
怎么让 GraphQL 支持 Laravel 自带的 where/in/orderBy 参数
GraphQL 原生不认 where[name_contains] 这种动态键名,也不支持数组参数直接透传给 whereIn()。硬写 resolver 拆参数太累,推荐两种方案:
- 用 Lighthouse 的
@whereConditions指令,配合WhereConditionsDirective,前端传where: { name_contains: "john" },后端自动转成->where('name', 'like', '%john%') - 在 rebing 中自定义一个
BaseQuery类,继承Query,统一处理$args['filter']数组,映射到where()、whereIn()、orderBy(),避免每个 Query 类重复写 - 注意:GraphQL 的
String类型不能直接当数组用,whereIn对应的参数得定义为Type::listOf(Type::string()),否则解析失败
最常被忽略的是字段权限控制——GraphQL 不会自动读 Gate::allows('view', $user),哪怕你写了 Policy。必须显式在 resolver 里调用,或用 Lighthouse 的 @can、rebing 的 middleware 绑定。漏这步,email 或 password_hash 字段可能直接暴露。


















