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.

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
Lucky Media logo

Lucky Media

Get Lucky Now - the ideal choice for Laravel Development, with over a decade of experience!

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
LaraJobs logo

LaraJobs

The official Laravel job board

LaraJobs
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
MongoDB logo

MongoDB

Enhance your PHP applications with the powerful integration of MongoDB and Laravel, empowering developers to build applications with ease and efficiency. Support transactional, search, analytics and mobile use cases while using the familiar Eloquent APIs. Discover how MongoDB's flexible, modern database can transform your Laravel applications.

MongoDB

The latest

View all →
Add Comments to your Laravel Application with the Commenter Package image

Add Comments to your Laravel Application with the Commenter Package

Read article
Laravel Advanced String Package image

Laravel Advanced String Package

Read article
Take the Annual State of Laravel 2024 Survey image

Take the Annual State of Laravel 2024 Survey

Read article
Upload Files Using Filepond in Livewire Components image

Upload Files Using Filepond in Livewire Components

Read article
The Best Laravel Tutorials and Resources for Developers image

The Best Laravel Tutorials and Resources for Developers

Read article
Introducing Built with Laravel image

Introducing Built with Laravel

Read article