Archives

MY APPROACH

The right solution, not the biggest solution.

You don't need to know whether your problem needs an API, an automation workflow, a dashboard or a custom application.

Tell me the problem. I'll help figure out a practical way to solve it.

API Resources vs toArray(): Stop Returning DB Models Directly

Returning an Eloquent model directly from a controller feels convenient:

return $user;

Laravel turns the model into JSON for you. Problem solved, right?

Not quite. You have just allowed your database model to become your API contract. That shortcut can expose fields you never intended to expose and makes future database changes harder.

In Laravel 13, API Resources give you a clean boundary between your Eloquent models and the JSON your API actually exposes.

The Convenient Approach: Return the Model

Imagine a typical User model:

class User extends Model
{
  protected $fillable = [
   'name',
   'email',
   'password',
   'phone',
  ];
}

Your controller might return it directly:

public function show(User $user)
{
  return $user;
}

Laravel's Eloquent models implement JSON serialization, so the framework can convert the model and its loaded relationships into an API response.

It works. That's exactly why this pattern survives in so many applications.

The problem appears later when the model grows.

Why Returning Eloquent Models Directly Is a Bad API Boundary

Your database model and your API response have different responsibilities.

A database model represents how your application stores and works with data. An API response represents what you have decided to expose to another system.

Those two shapes often start out looking identical. They rarely stay that way.

Suppose the users table eventually contains:

id
name
email
password
remember_token
email_verified_at
internal_notes
is_admin
created_at
updated_at

You probably don't want all of those fields becoming part of your public API.

Even if hidden attributes protect some sensitive fields, you are still coupling your API representation to your persistence model.

toArray() Is Not the Same as an API Resource

Eloquent provides toArray() to convert a model into an array:

$user = User::findOrFail($id);
return response()->json(
  $user->toArray()
);

This is useful when you genuinely need an array representation of the model. But it still leaves your response closely tied to the model's serialization behavior.

You can override toArray():

public function toArray(Request $request): array
{
  return [
    'id' => $this->id,
    'name' => $this->name,
    'email' => $this->email,
  ];
}

The problem is that this method belongs to the model. It is not specifically an API presentation layer.

A model may be used by a web application, command, job, export, integration, and multiple API endpoints. Making its toArray() method represent one particular API contract can create awkward coupling.

Laravel 13 API Resources: Put the API Contract in the Right Place

Laravel's API Resources provide a dedicated transformation layer between Eloquent models and API responses.

Generate one with Artisan:

php artisan make:resource UserResource

The resource can explicitly define the response:

namespace App\Http\Resources;

use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;

class UserResource extends JsonResource
{
  public function toArray(Request $request): array
  {
    return [
     'id' => $this->id,
     'name' => $this->name,
     'email' => $this->email,
   ];
  }
}

Your controller now returns:

public function show(User $user)
{
  return new UserResource($user);
}

Now the boundary is explicit:

Database Model
  ↓
Eloquent
  ↓
UserResource
  ↓
JSON API

The model knows about persistence and relationships. The resource knows about the external representation.

Resources Make Sensitive Fields Explicit

One of the biggest advantages is that you define what leaves your application.

Instead of asking:

"Did I accidentally expose a new model attribute?"

you have an allow-list:

return [
 'id' => $this->id,
 'name' => $this->name,
 'email' => $this->email,
];

If someone later adds internal_notes to the model, it doesn't automatically become part of this resource.

That's a much better default for a public API: expose intentionally rather than expose accidentally.

Relationships Without Creating Another N+1 Problem

API Resources become particularly useful when your response contains relationships.

Suppose a post belongs to an author:

class Post extends Model
{
  public function author(): BelongsTo
  {
    return $this->belongsTo(User::class, 'author_id');
  }
}

You could expose the author through another resource:

return [
  'id' => $this->id,
  'title' => $this->title,
  'author' => new UserResource($this->whenLoaded('author')),
];

Then load the relationship in the query:

$posts = Post::with('author')
  ->latest()
  ->paginate(20);
return PostResource::collection($posts);

This is an important combination: eager loading controls database access, while Resources control the API representation.

Don't assume that using a Resource automatically fixes an N+1 query problem. If the Resource accesses an unloaded relationship, you can still trigger unnecessary queries.

Conditional Relationships With whenLoaded()

Sometimes an endpoint should include a relationship only when the controller explicitly loaded it.

return [
 'id' => $this->id,
 'title' => $this->title,
 'author' => new UserResource(
    $this->whenLoaded('author')
 ),
];

This keeps the resource from blindly assuming that every caller loaded every relationship.

The result is a useful separation of responsibility:

  • Query: decide what data needs to be fetched.
  • Resource: decide what data should be exposed.

Resources Also Protect You From API Shape Drift

Imagine your frontend currently expects:

{
  "id": 42,
  "name": "Rashid",
  "email": "rashid@example.com"
}

Six months later, you rename a database column or restructure an Eloquent relationship.

If your API simply mirrors the model, persistence changes can unexpectedly affect consumers.

With a Resource, you can keep the API stable:

return [
  'id' => $this->id,
  'name' => $this->display_name,
  'email' => $this->email,
];

The database can change while the API contract remains the same.

That boundary becomes increasingly valuable when your API has mobile clients, frontend applications, third-party integrations, or multiple versions.

What About toArray()?

toArray() is not bad. The mistake is using it as a substitute for a deliberate API representation in every situation.

It is perfectly reasonable when you need the model's array representation internally:

$data = $user->toArray();

The question is what boundary you are crossing.

  • Internal model transformation: toArray() can be appropriate.
  • Public or structured API response: prefer an API Resource.
  • Complex external contract: use a dedicated Resource or DTO depending on the application's needs.

Production Gotchas

1. Don't put business logic in Resources

A Resource should transform data, not decide whether an order should be cancelled or calculate complex business rules.

Keep business decisions in your domain or service layer and let the Resource present the resulting data.

2. Don't blindly expose every relationship

This:

'comments' => CommentResource::collection($this->comments),

may look harmless, but it can trigger additional queries and produce a very large response.

Prefer:

'comments' => CommentResource::collection(
  $this->whenLoaded('comments')
),

and explicitly eager load the relationship only for endpoints that need it.

3. Don't confuse Resources with DTOs

Both can define data boundaries, but they solve different problems.

An API Resource is primarily concerned with transforming application data into an HTTP/API representation. A DTO is useful when you need a structured data object passed between application layers, commands, services, or external integrations.

A Clean Laravel API Pattern

For a typical CRUD API, a clean flow looks like this:

Request
  ↓
Controller
  ↓
Query / Service
  ↓
Eloquent Model
  ↓
API Resource
  ↓
JSON Response

For example:

public function show(User $user): UserResource
{
  return new UserResource($user);
}

And for a collection:

public function index(): AnonymousResourceCollection
{
  $users = User::query()
    ->latest()
    ->paginate(20);
  return UserResource::collection($users);
}

The controller stays small because it doesn't need to manually build JSON arrays or expose the entire Eloquent model.

The Rule I Use in Code Reviews

When I see this:

return $model;

I ask one question:

"Is this model the API contract, or are we accidentally making it one?"

If the endpoint is a real API consumed outside the immediate application code, I generally want an explicit Resource.

It costs a few lines. In return, the API has a defined shape, sensitive attributes are not accidentally exposed, relationships can be controlled, and database changes don't automatically become API changes.

Final Takeaway

Eloquent models are excellent at representing your application's persistence layer. They don't need to become your public API contract.

toArray() is useful for converting a model into an array. API Resources go one step further: they define what your API should actually expose.

So the next time you write:

return $user;

stop for a second.

If that response crosses an API boundary, make the contract explicit:

return new UserResource($user);

That small boundary pays off the moment your database, API, or team gets bigger.

 

 

Comments

No comments yet. Be the first to share your thoughts.

Leave a comment

RDA
Rashid Anwar
Independent Developer

I’m Rashid, an independent developer focused on practical software solutions for businesses.

HAVE A PROJECT IN MIND?

Let's talk about the problem you're trying to solve.

Share what you need. We can discuss the requirements and a practical next step.

Start a Conversation