Every Laravel route or controller method returns something that gets converted into an HTTP response — understanding the different ways to build that response, and when to reach for each, avoids a lot of trial and error.
The simplest case: returning a string
Route::get('/ping', fn () => 'pong');
Laravel automatically wraps a returned string in a 200 response with a text/html content type.
The response() helper
return response('Not allowed', 403);
response() takes the body as the first argument and the status code as the second — the most common way to return a specific status code with a simple body.
JSON responses
return response()->json(['status' => 'ok', 'id' => $order->id]);
This sets the Content-Type: application/json header and serializes the array automatically. Returning an Eloquent model or collection directly from a route also auto-converts to JSON — response()->json() is only necessary when you need to also set a custom status code or headers.
Setting headers and cookies fluently
return response('Created', 201)
->header('X-Request-Id', $requestId)
->cookie('last_visit', now()->toDateString());
Redirects
return redirect()->route('dashboard');
return redirect()->back()->withErrors(['email' => 'Already taken']);
The underlying Response class
Every one of these shortcuts ultimately produces an Illuminate\Http\Response (or a subclass like JsonResponse or RedirectResponse) — reaching for the class directly is rarely necessary in application code, but it's worth knowing it's there when a package or test needs to inspect exactly what a route returned.