Laravel Response Classes

Published on by

Laravel Response Classes image

Responding from your Laravel application is what I would call vital, especially when you are building an API. Let's have a look at how we can power up our responses.

Many of us typically start with using the helper functions in our applications, as the docs and many tutorials will use them. They are easy to start with and do exactly what you expect them to do. Let's take a look at what these look like:

return response()->json(
data: [],
status: 200,
);

This is a slightly exaggerated example; you usually send data through and skip the status code. However, for me, habits die hard!

This code will create a new JsonResponse for you and pass in the data and status code ready for you to return. This works, and there is nothing wrong with using this approach. If you are using this already, a way to step up your API game here is to add the status code to be more declarative in what you are returning.

Moving forwards, we can skip using helper functions and start using the underlying class that the helper functions create:

return new JsonResponse(
data: [],
status: 200,
);

I like this approach as it relies less on helpers and is more declarative. Looking at the code, you know exactly what is being returned because it is right in front of you instead of being abstracted behind a helper. You can level this up by using a constant or another way to declare the status code itself - making this even more accessible to read and understand for developers who may not know all the status codes by heart. Let's see what that might look like:

return new JsonResponse(
data: [],
status: JsonResponse::HTTP_OK,
);

The JsonResponse class extends the Symfony Response class through a few layers of abstraction so that you can call this directly - however, your static analyzer may complain about this. I built a package called juststeveking/http-status-code, a PHP Enum that will return something similar, and its only job is to return status codes. I prefer this more lightweight utility approach to things like this, as you know exactly what is happening and what this class or package may do. The problem sometimes is that the class you are using does so much that you have to load this huge thing into memory just to be able to return an integer value. This doesn't make much sense, so I recommend using a dedicated package or class to manage this yourself. Let's see what it looks like when you do that:

return new JsonResponse(
data: [],
status: Http::OK->value,
);

This is a significant step forward in terms of clarity of how declarative our code is. It is easy to read and understand exactly what is going on. However, we find ourselves creating the same block of code time after time, so how can we resolve this?

The answer is quite a simple one - Response classes. In Laravel, there is a contract we can use called Responsable, which tells us that our class must have a toResponse method on it. We can return this directly from our controllers, as Laravel will parse and understand these classes with no problems. Let us look at a quick basic example of what these classes look like:

class MyJsonResponse implements Responsable
{
public function __construct(
public readonly array $data,
public readonly Http $status = Http::OK,
) {}
 
public function toResponse($request): Response
{
return new JsonResponse(
data: $this->data,
status: $this->status->value,
);
}
}

This is something simple to use. However, it isn't adding any value to our application. It is just an abstraction around what is already there. Let's look at something that might add more value to our application.

class CollectionResponse implements Responsable
{
public function __construct(
public readonly JsonResourceCollection $data,
public readonly Http $status = Http::OK,
) {}
 
public function toResponse($request): Response
{
return new JsonResponse(
data: $this->data,
status: $this->status->value,
);
}
}

Now we have a response class that will handle any resource collections we pass through, making it very reusable for our application. Let's look at how we might return this in our controller:

return new CollectionResponse(
data: UserResource::collection(
resource: User::query()->get(),
),
);

It is cleaner, has less code duplication, and is easy to override the default status should we need to. It gives us the benefit that the helper methods and Json Response class gave us - but allows us more context and predictability.

However, we are now faced with the problem of code duplication in other areas. Within our response classes themselves. Many of these look similar, the only difference being that the constructor properties will be different types. We want to keep the context of using custom response classes, but we want to avoid creating something with a vast union-type argument for a property - when we may as well add mixed and be done with it.

In this situation, you can either reach for an abstract class to extend or a trait to add the behavior to classes that need it. Personally, I am a fan of composition over inheritance, so using a trait makes more sense to me.

trait SendsResponse
{
public function toResponse($request): Response
{
return new JsonResponse(
data: $this->data,
status: $this->status->value,
);
}
}

The biggest problem with this approach is that static analysis will complain about this code because the trait needs to have or know about the properties of the class. This is something of an easy fix, though.

/**
* @property-read mixed $data
* @property-read Http $status
*/

We can add this doc block to the trait so that it is aware of properties that it has access to.

Now our Response classes will be a lot simpler to use and build, with less repetition in our code.

class MessageResponse implements Responsable
{
use SendsResponse;
 
public function __construct(
public readonly array $data,
public readonly Http $status = Http::OK,
) {}
}

Now we can build out all the potential responses that we need to send easily, keeping type safety up and code duplication down.

Steve McDougall photo

Technical writer at Laravel News, Developer Advocate at Treblle. API specialist, veteran PHP/Laravel engineer. YouTube livestreamer.

Cube

Laravel Newsletter

Join 40k+ other developers and never miss out on new tips, tutorials, and more.

image
Laravel Forge

Easily create and manage your servers and deploy your Laravel applications in seconds.

Visit Laravel Forge
Laravel Forge logo

Laravel Forge

Easily create and manage your servers and deploy your Laravel applications in seconds.

Laravel Forge
Tinkerwell logo

Tinkerwell

The must-have code runner for Laravel developers. Tinker with AI, autocompletion and instant feedback on local and production environments.

Tinkerwell
No Compromises logo

No Compromises

Joel and Aaron, the two seasoned devs from the No Compromises podcast, are now available to hire for your Laravel project. ⬧ Flat rate of $7500/mo. ⬧ No lengthy sales process. ⬧ No contracts. ⬧ 100% money back guarantee.

No Compromises
Kirschbaum logo

Kirschbaum

Providing innovation and stability to ensure your web application succeeds.

Kirschbaum
Shift logo

Shift

Running an old Laravel version? Instant, automated Laravel upgrades and code modernization to keep your applications fresh.

Shift
Bacancy logo

Bacancy

Supercharge your project with a seasoned Laravel developer with 4-6 years of experience for just $2500/month. Get 160 hours of dedicated expertise & a risk-free 15-day trial. Schedule a call now!

Bacancy
LoadForge logo

LoadForge

Easy, affordable load testing and stress tests for websites, APIs and databases.

LoadForge
Paragraph logo

Paragraph

Manage your Laravel app as if it was a CMS – edit any text on any page or in any email without touching Blade or language files.

Paragraph
Lucky Media logo

Lucky Media

Bespoke software solutions built for your business. We ♥ Laravel

Lucky Media
Lunar: Laravel E-Commerce logo

Lunar: Laravel E-Commerce

E-Commerce for Laravel. An open-source package that brings the power of modern headless e-commerce functionality to Laravel.

Lunar: Laravel E-Commerce
DocuWriter.ai logo

DocuWriter.ai

Save hours of manually writing Code Documentation, Comments & DocBlocks, Test suites and Refactoring.

DocuWriter.ai
LaraJobs logo

LaraJobs

The official Laravel job board

LaraJobs
All Green logo

All Green

All Green is a SaaS test runner that can execute your whole Laravel test suite in mere seconds so that you don't get blocked – you get feedback almost instantly and you can deploy to production very quickly.

All Green
Larafast: Laravel SaaS Starter Kit logo

Larafast: Laravel SaaS Starter Kit

Larafast is a Laravel SaaS Starter Kit with ready-to-go features for Payments, Auth, Admin, Blog, SEO, and beautiful themes. Available with VILT and TALL stacks.

Larafast: Laravel SaaS Starter Kit
SaaSykit: Laravel SaaS Starter Kit logo

SaaSykit: Laravel SaaS Starter Kit

SaaSykit is a Laravel SaaS Starter Kit that comes with all features required to run a modern SaaS. Payments, Beautiful Checkout, Admin Panel, User dashboard, Auth, Ready Components, Stats, Blog, Docs and more.

SaaSykit: Laravel SaaS Starter Kit
Rector logo

Rector

Your partner for seamless Laravel upgrades, cutting costs, and accelerating innovation for successful companies

Rector

The latest

View all →
Automatic Blade Formatting on Save in PhpStorm image

Automatic Blade Formatting on Save in PhpStorm

Read article
PhpStorm 2024.1 Is Released With a Integrated Terminal, Local AI Code Completion, and More image

PhpStorm 2024.1 Is Released With a Integrated Terminal, Local AI Code Completion, and More

Read article
Laravel Prompts Adds a Multi-line Textarea Input, Laravel 11.3 Released image

Laravel Prompts Adds a Multi-line Textarea Input, Laravel 11.3 Released

Read article
Bartender Is an Opinionated Way to Authenticate Users Using Laravel Socialite image

Bartender Is an Opinionated Way to Authenticate Users Using Laravel Socialite

Read article
Jeffrey Way's PhpStorm Setup in 2024 image

Jeffrey Way's PhpStorm Setup in 2024

Read article
Easily Optimize PDFs in Laravel with the Optimizer Package image

Easily Optimize PDFs in Laravel with the Optimizer Package

Read article