DEV Community

Cover image for Laravel Doesn’t Need GraphQL Part 1: Use DTOs and AQC for Exact Data
Raheel Shan
Raheel Shan

Posted on • Originally published at raheelshan.com

Laravel Doesn’t Need GraphQL Part 1: Use DTOs and AQC for Exact Data

GraphQL looks shiny, but behind that shine is a pile of extra rules, schemas, and resolvers. If you’re a Laravel dev, you don’t need all that ceremony. You can get GraphQL-level precision using tools you already have: DTOs and AQC.

GraphQL gets a lot of hype because it lets the frontend ask for exactly the fields it needs—no more, no less. That sounds neat. But it also comes with rules, schemas, resolvers, and an entire layer you now have to learn and maintain on top of your backend.

Here’s the thing: if you’re already a Laravel developer, you don’t need GraphQL at all. With DTOs (Data Transfer Objects) and the AQC (Atomic Query Construction) pattern, you can achieve the same precision without adding another language to your stack.

How GraphQL Works (and Why It Feels Heavy)

1. Frontend initializes the query

In GraphQL, the frontend decides which fields it wants:

{
  users {
    id
    name
    email
  }
}
Enter fullscreen mode Exit fullscreen mode

The request itself defines the data shape.

2. Backend goes through the GraphQL layer

Every request has to be parsed, validated, and resolved according to GraphQL’s schema. Developers must learn and respect GraphQL’s conventions, resolvers, and quirks.

3. Response is always JSON

The backend returns JSON shaped exactly like the query asked for.

That’s powerful, but it’s also extra machinery that Laravel developers don’t necessarily need.


The Laravel-Native Alternative

Now let’s contrast this with a DTO + AQC approach.

1. Frontend makes a request (but not the query)

The frontend doesn’t decide the fields. It just hits an API endpoint—simple and familiar.

2. Backend uses Laravel patterns you already know

In your controller or repository, you fetch data the same way you always have—through Eloquent models, repositories, or AQC classes. Nothing alien here. The difference is that you pipe results into DTO classes.

class BasicUser extends BaseDTO {
    public int $id;
    public string $name;
    public string $email;
}
Enter fullscreen mode Exit fullscreen mode

DTOs act as strict contracts. Only the fields you define here get selected and returned, no matter how messy your models are.

BaseDTO class

abstract class BaseDTO
{
    public static function columns(): array
    {
        return array_map(
            fn($prop) => $prop->getName(),
            (new \ReflectionClass(static::class))->getProperties()
        );
    }
}
Enter fullscreen mode Exit fullscreen mode

And here's how to select with Eloquent.

class UserController extends Controller
{
    public function index(Request $request)
    {
        $users = User::select(BasicUser::columns())->get();

        if($request->ajax()){
            return response()->json($users);
        }

        return view('user.index', ['users' => $users]);
    }
}
Enter fullscreen mode Exit fullscreen mode

Here's an example with the AQC design pattern.

namespace App\AQC\User;

use App\Models\User;

class GetUsers
{
    public static function handle($params = [], $paginate = true, $columns = '*')
    {
        $userObj = User::latest('id');

        if (isset($params['is_active']) && $params['is_active'] > 0) {
            $userObj->where('is_active', $params['is_active']);
        }

        // add more conditions for different use cases

        $userObj->select($columns);

        return $paginate
            ? $userObj->paginate(User::PAGINATE)
            : $userObj->get();
    }
}
Enter fullscreen mode Exit fullscreen mode

3. Response can be JSON or HTML

Unlike GraphQL, you’re not locked to JSON. Sometimes JSON makes sense:

return response()->json($users);

But sometimes you want ready-to-render HTML in your api endpoint:

// pre-rendered html response instead of json 
return view('user.index', ['users' => $users]);
Enter fullscreen mode Exit fullscreen mode

The frontend just injects that HTML fragment into the DOM. No need for JSON to HTML rendering.

Why DTOs and AQC Over GraphQL for Laravel

  • No extra layer: You stay inside Laravel. No resolvers, no schema language, no GraphQL servers.
  • Familiar workflow: API controllers, repositories, models—the same workflow Laravel devs already live in.
  • Strict contracts: DTOs ensure only the fields you want ever leave your backend. That’s your GraphQL-like precision.
  • Flexible responses: JSON for APIs or Blade-rendered HTML fragments for fast DOM updates. GraphQL doesn’t give you this choice.
  • Developer focus: The backend decides what’s appropriate. The frontend doesn’t dictate your database access strategy.

DTO + AQC = GraphQL Precision, Laravel Simplicity

GraphQL’s promise is “fetch only what you need.” With DTOs and AQC, you can deliver that promise without dragging another layer into your backend.

  • DTOs define the contract.
  • AQC makes queries explicit and efficient.
  • Responses can be JSON or HTML, depending on what your frontend actually needs.

So, skip the extra learning curve. Laravel doesn’t need GraphQL—you can already do it all with DTOs and AQC.

Caveats

This isn’t some universal silver bullet. DTOs and AQC just give you stricter discipline. If your queries have nested relations or conditional logic, you’ll still need to be smart about eager loading and selecting the right columns. The benefit is that DTOs make those choices explicit instead of letting you accidentally grab half the database.

And no, I don’t recommend dumping this into a giant legacy project. Start clean on new projects or introduce it slowly. You’ll see the payoff where it matters: fewer columns, tighter contracts, and less accidental over-fetching.

Final Thoughts

GraphQL is not the only way to fetch the required data. This is an alternate approach for developers who don't want to learn a new layer.


If you found this post helpful, consider supporting my work—it means a lot.

Support my work

Top comments (0)