Skip to content
PHP

Laravel JSON API Responses: Return the Right Data and Handle Errors Cleanly

·5 min read
Laravel JSON API Responses: Return the Right Data and Handle Errors Cleanly - PHP guide from JSON Prettifier

Quick answer: Laravel returns JSON with response()->json($data, $status), which sets the body, the HTTP status, and the Content-Type: application/json header in one call. The harder part is agreeing on a consistent data structure, using status codes that describe the outcome (200, 201, 204, 404, 422), and returning validation errors in a shape your clients can handle. API Resources keep that shape stable as the application grows.

A React or Vue frontend calls your Laravel API. The endpoint returns HTTP 200, so everything seems fine—until the client discovers that sometimes data is an array, sometimes an object, and sometimes the error response is an HTML page.

Explore the resulting JSON in the JSON Viewer, or select nested fields with the JSONPath Query Tester.

Your first JSON response

A simple Laravel controller action can return:

use Illuminate\Http\JsonResponse;

public function show(): JsonResponse
{
    return response()->json([
        'data' => [
            'id' => 1042,
            'status' => 'paid',
            'total' => 249.50,
        ],
    ]);
}

The response body is JSON:

{
  "data": {
    "id": 1042,
    "status": "paid",
    "total": 249.5
  }
}

The PHP float 249.50 serializes as 249.5 because JSON has no notion of trailing zeros. For money, don't assume a floating-point number preserves display formatting or exact decimal arithmetic. Depending on your system, store minor currency units as integers or use a decimal representation deliberately. The JSON numbers and precision guide covers what survives the round trip and what does not, and PHP json_encode vs json_decode explains the encoding flags Laravel passes under the hood.

Return the correct status code

The browser is not the only consumer of your API. An integration, mobile application, or monitoring service often relies on HTTP status codes.

SituationTypical status
Read a resource successfully200 OK
Create a resource successfully201 Created
Delete without a response body204 No Content
Validation failure422 Unprocessable Content (Laravel convention)
Missing resource404 Not Found
Authentication required401 Unauthorized

For a created record:

return response()->json([
    'message' => 'Order created',
    'data' => ['id' => 1042],
], 201);

A 204 response should not contain a JSON body. Laravel has a helper for exactly this case:

use Illuminate\Http\Response;

public function destroy(Order $order): Response
{
    $order->delete();

    return response()->noContent();
}

On the frontend, do not call response.json() blindly for an empty 204 response.

Validate before saving

Let's say an endpoint accepts a customer's name and quantity. Validate the input rather than trusting that a syntactically valid JSON document contains reasonable data.

use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse;

public function store(Request $request): JsonResponse
{
    $data = $request->validate([
        'name' => ['required', 'string', 'max:100'],
        'quantity' => ['required', 'integer', 'min:1'],
    ]);

    // Persist the validated values using your service/model.
    return response()->json([
        'message' => 'Input accepted',
        'data' => $data,
    ], 201);
}

When the request expects JSON, a failed validate() call throws a ValidationException that Laravel renders as a 422 response with a fixed shape:

{
  "message": "The name field is required. (and 1 more error)",
  "errors": {
    "name": ["The name field is required."],
    "quantity": ["The quantity field must be at least 1."]
  }
}

For a traditional HTML form request, the same failure redirects back with the errors flashed to the session. Which branch Laravel takes depends on the request headers, so make sure API clients send Accept: application/json and Content-Type: application/json. If you want to design your own envelope for non-validation errors, the JSON API error response format guide compares the common conventions.

Why a frontend can receive HTML unexpectedly

An endpoint can return HTML when it redirects to login, fails before API handling, or routes to the wrong page. Laravel's exception handler uses the request's Accept header (via $request->expectsJson()) to decide whether an exception becomes a JSON payload or an HTML error page, and the authentication middleware uses the same signal to decide between a 401 JSON response and a redirect. Then the client does this:

const response = await fetch('/api/orders/1042');
const data = await response.json();

If the body begins with <html, the parse fails. That doesn't mean the JSON formatter is broken; the API returned a different format.

A more defensive pattern checks the response:

const response = await fetch('/api/orders/1042', {
  headers: { Accept: 'application/json' }
});

if (!response.ok) {
  throw new Error(`API request failed (${response.status})`);
}

const contentType = response.headers.get('content-type') || '';
if (!contentType.includes('application/json')) {
  throw new Error('Expected JSON, received another content type');
}

const data = await response.json();

In real applications, you should also define a consistent error envelope and handle network failures. For this example, the main lesson is to check what the server actually returned before parsing it. The fetch JSON in JavaScript guide walks through the full client-side pattern, and the JSON Content-Type guide explains why application/json is the header both sides should agree on.

Use API Resources when your responses grow

A small array is fine for a tutorial. In a real application, Laravel API Resources can separate a model's internal database structure from its public API representation. That gives you a place to rename fields, conditionally include relationships, and keep responses consistent as code evolves.

use Illuminate\Http\Resources\Json\JsonResource;

class OrderResource extends JsonResource
{
    public function toArray($request): array
    {
        return [
            'id' => $this->id,
            'status' => $this->status,
            'totalMinorUnits' => $this->total_minor_units,
            'customer' => new CustomerResource($this->whenLoaded('customer')),
        ];
    }
}

// In the controller:
return new OrderResource($order);                       // single resource
return OrderResource::collection($orders);              // list
return (new OrderResource($order))->response()->setStatusCode(201);

Resources wrap their output in a data key by default, which is what gives every endpoint the same top-level shape. If your frontend prefers flat responses, call JsonResource::withoutWrapping() once in a service provider rather than unwrapping in individual controllers.

Test at least the successful response, validation failure, unauthorized request, missing resource, and no-content case. A browser-based JSON formatter and validator can help inspect sanitized example responses, but Laravel feature tests should assert behavior automatically.

You may also find our JSON in REST APIs guide and JSON parse error guide helpful while debugging integrations.

A good Laravel API isn't measured by how quickly it returns an object. It's measured by how easily another developer can consume it without guessing what happens on failure.

Frequently Asked Questions

How do I return JSON from a Laravel controller?
Call response()->json($data, $status) to set the body, the status code, and the Content-Type header in one place. Returning a plain array, an Eloquent model, or a collection from a route also serializes to JSON automatically, but the explicit helper makes the status code obvious.
What status code does Laravel return for failed validation?
When the request expects JSON, $request->validate() throws a ValidationException that Laravel renders as a 422 response with a message key and an errors object keyed by field name. For a normal form request it redirects back with the errors in the session instead.
Why does my Laravel API return HTML instead of JSON?
Laravel decides between JSON and HTML using the request's Accept header. Without Accept: application/json, an unauthenticated request can redirect to the login page and an exception can render as an HTML error page. Send the header from every API client, or put the routes behind middleware that forces JSON.
What is a Laravel API Resource?
An API Resource (a class extending JsonResource) is a transformation layer between an Eloquent model and the JSON your API exposes. It lets you rename fields, hide internal columns, and include relationships conditionally, and it wraps single resources and collections in a data key by default.
How do I return a 204 No Content response in Laravel?
Use response()->noContent(), which sends status 204 with an empty body. Do not pass a JSON payload with a 204, and make sure the client does not call response.json() on the empty body.

Inspect a Laravel response before you debug it

Paste the raw body from Postman or the Network tab into JSON Prettifier to format it, validate it, and sort keys so fields are easy to compare across responses. Everything runs in your browser; nothing is uploaded.

Format a JSON response