真正卡住人的三处是中间件链、异常路径未拦截、模型序列化边界;API路由须定义在routes/api.php,状态码需显式传入response()->json(),所有异常必须经Handler::render()统一处理,模型字段需正确配置可见性与格式。

直接用 response()->json() 就能返回 JSON,但“统一格式”不是加个函数调用就完事——真正卡住人的,是中间件链、异常路径没拦截、模型序列化边界这三处。
API 路由必须走 routes/api.php,否则返回 HTML
Laravel 的 api 中间件组自带 EnsureFrontendRequestsAreStateful 和自动 JSON 协商能力。如果把 API 路由写在 routes/web.php 里,哪怕加了 ->middleware('api'),也可能被 session 或 CSRF 中间件劫持,最终返回 HTML 页面而非 JSON。
- 确认路由定义在
routes/api.php文件中(该文件默认已绑定api中间件组) - 手动测试时用
curl -H "Accept: application/json" http://localhost:8000/api/users,避免浏览器直输(不带Accept头) - 不要在
web.php里混写 API 路由;若必须,得确保没被web中间件包裹(比如误写成->middleware('web'))
response()->json() 的第三个参数别漏:状态码要显式传
很多接口创建后仍返回 200,前端收不到 201,是因为忘了传状态码。REST 规范要求资源创建成功必须用 201,删除成功用 204(且不能带响应体)。
- 创建成功:
return response()->json(['data' => $user], 201); - 删除成功:
return response()->json([], 204);(注意:空数组也不行,204 响应体必须为空) - 验证失败:
return response()->json(['message' => 'Validation failed', 'errors' => $validator->errors()], 422); - 别把
json_encode()结果再传给response()->json(),会导致双重编码(如"{\"user\":{...}}")
所有异常路径都得进 Handler::render(),否则格式崩了
控制器里写了统一结构,但 ModelNotFoundException、ValidationException、AuthenticationException 这些根本不会经过你的控制器——它们由 App\Exceptions\Handler 拦截。不重写 render(),前端就会收到 HTML 错误页或裸数组。
- 在
app/Exceptions/Handler.php的render()方法开头加判断:if ($request->expectsJson()) { ... } - 对
ModelNotFoundException,返回response()->json(['message' => 'Not found'], 404),别用默认英文提示 - 对
ValidationException,提取$exception->errors(),包装成你定义的code/message/data结构 - 对
AuthenticationException,可复写unauthenticated()方法,返回带code字段的 401 响应
模型转 JSON 前先确认字段可见性与时间格式
Eloquent 模型默认不输出 password、remember_token,但如果你用了 $appends 或自定义 toArray(),又没预加载关联关系,很容易触发 N+1 或字段缺失。
- 检查
$hidden和$casts:比如protected $casts = ['created_at' => 'datetime:Y-m-d H:i:s']; - 动态字段(如
is_subscribed)必须声明在$appends并提供getIsSubscribedAttribute()方法 - 别在 ApiResource 的
toArray()里写数据库查询;预加载应在控制器完成:User::with('posts')->findOrFail($id) - 分页响应要用
$users->toArray()后手动补meta字段,JsonResource::collection()不会自动带分页元信息
最常被跳过的其实是 Handler::render() 里的 $request->expectsJson() 判断——只要这里漏掉,任何异常都会退回到 HTML 或调试页面,前端 fetch 就静默失败。中间件、状态码、模型序列化,三者缺一不可。


















