DEV Community

Cover image for Build Complete REST APIs in Minutes with Laravel Query Gate
Jefferson Silva
Jefferson Silva

Posted on

Build Complete REST APIs in Minutes with Laravel Query Gate

Every Laravel developer knows the drill: create a model, write a controller, define routes, add validation, implement filtering, pagination, sorting... rinse and repeat for every single entity in your application.

What if I told you there's a better way?

I've been working on Laravel Query Gate, a package that transforms how you build APIs. Instead of writing repetitive boilerplate code, you declare what you want — and Query Gate handles the rest.

Let me show you how I built a complete blog API with Posts, Comments, Categories, Tags, Authentication, Custom Actions, and API Versioning — all without writing a single controller.

The Problem We All Face

Here's what a typical Laravel API setup looks like:

// PostController.php - Just ONE of many controllers you'll write
class PostController extends Controller
{
    public function index(Request $request)
    {
        $query = Post::query();

        if ($request->has('status')) {
            $query->where('status', $request->status);
        }
        if ($request->has('category_id')) {
            $query->where('category_id', $request->category_id);
        }
        if ($request->has('author')) {
            $query->whereHas('author', fn($q) => $q->where('name', 'like', "%{$request->author}%"));
        }
        // ... 20 more filters

        if ($request->has('sort')) {
            // parsing logic...
        }

        return $query->paginate($request->per_page ?? 15);
    }

    public function store(Request $request)
    {
        $validated = $request->validate([
            'title' => 'required|string|max:255',
            'content' => 'required|string',
            // ... more rules
        ]);

        return Post::create($validated);
    }

    // update(), destroy(), show()... you get the idea
}
Enter fullscreen mode Exit fullscreen mode

Now multiply this by every model in your application. It's exhausting.

The Query Gate Way

Here's the same functionality with Laravel Query Gate:

// app/Models/Post.php
class Post extends Model
{
    use HasQueryGate;

    public static function queryGate(): QueryGate
    {
        return QueryGate::make()
            ->alias('posts')
            ->middleware(['auth:sanctum'])
            ->filters([
                'status' => ['string', 'in:draft,published,archived'],
                'category_id' => 'integer',
                'author.name' => ['string', 'max:100'],
                'created_at' => 'date',
            ])
            ->allowedFilters([
                'status' => ['eq', 'in', 'neq'],
                'category_id' => ['eq', 'in'],
                'author.name' => ['like'],
                'created_at' => ['gte', 'lte', 'between'],
            ])
            ->select(['id', 'title', 'slug', 'status', 'created_at', 'author.name'])
            ->sorts(['created_at', 'title'])
            ->actions(fn ($actions) => $actions
                ->create(fn ($action) => $action
                    ->validations([
                        'title' => ['required', 'string', 'max:255'],
                        'content' => ['required', 'string'],
                    ])
                )
                ->update()
                ->delete()
            );
    }
}
Enter fullscreen mode Exit fullscreen mode

That's it. No controller. No routes. No repetitive code.

Query Gate automatically gives you:

GET    /query/posts              → List with filters, sorting, pagination
GET    /query/posts/{id}         → Show single post
POST   /query/posts              → Create post
PATCH  /query/posts/{id}         → Update post
DELETE /query/posts/{id}         → Delete post
Enter fullscreen mode Exit fullscreen mode

Real-World Example: A Complete Blog API

I built a complete example project to showcase what Query Gate can do. Here's what's included:

1. Advanced Filtering

Filter by any field with operators like eq, neq, like, in, between, gt, gte, lt, lte:

# Posts published in 2024
GET /query/posts?filter[published_at][between]=2024-01-01,2024-12-31

# Posts by specific tags
GET /query/posts?filter[tag_slugs][in]=laravel,php

# Posts with 1000+ views, sorted by popularity
GET /query/posts?filter[views_count][gte]=1000&sort=views_count:desc
Enter fullscreen mode Exit fullscreen mode

2. Relationship Filtering

Query through relationships using dot notation:

# Posts by author name
GET /query/posts?filter[author.name][like]=John

# Comments on published posts
GET /query/comments?filter[post.status][eq]=published
Enter fullscreen mode Exit fullscreen mode

3. Custom Actions

Need more than CRUD? Define custom actions:

// app/Actions/QueryGate/Posts/PublishPost.php
class PublishPost extends AbstractQueryGateAction
{
    public function action(): string
    {
        return 'publish';
    }

    public function handle($request, $model, array $payload)
    {
        $model->update([
            'status' => 'published',
            'published_at' => now(),
        ]);

        return ['message' => 'Post published!', 'post' => $model];
    }

    public function authorize($request, $model): ?bool
    {
        return $request->user()->can('update', $model);
    }
}
Enter fullscreen mode Exit fullscreen mode

Now you have:

POST /query/posts/1/publish
Enter fullscreen mode Exit fullscreen mode

The example includes actions for: Publish, Unpublish, Archive, Feature, Unfeature, Duplicate posts and Approve, Reject, Mark as Spam comments.

4. API Versioning (Yes, Built-In!)

Evolve your API without breaking existing clients:

QueryGate::make()
    ->version('2024-01-01', function (QueryGate $gate) {
        $gate->filters(['title' => 'string', 'status' => 'string'])
             ->select(['id', 'title', 'status']);
    })
    ->version('2025-01-01', function (QueryGate $gate) {
        $gate->filters([
            'title' => 'string',
            'status' => 'string',
            'views_count' => 'integer',  // New in 2025!
            'author.name' => 'string',   // New in 2025!
        ])
        ->select(['id', 'title', 'status', 'views_count', 'author.name']);
    });
Enter fullscreen mode Exit fullscreen mode

Clients choose their version:

GET /query/posts -H "X-Query-Version: 2024-01-01"
Enter fullscreen mode Exit fullscreen mode

And there's a changelog endpoint:

GET /query/posts/__changelog
Enter fullscreen mode Exit fullscreen mode

5. Automatic OpenAPI Documentation

Query Gate generates OpenAPI/Swagger docs automatically:

GET /query/docs      → Interactive documentation UI
GET /query/docs.json → OpenAPI JSON spec
Enter fullscreen mode Exit fullscreen mode

No manual documentation. It's always in sync with your code.

Quick Start

Want to try it? Here's how to get started:

Install the Package

composer require behindsolution/laravel-query-gate
Enter fullscreen mode Exit fullscreen mode

Add the Trait to Your Model

use BehindSolution\LaravelQueryGate\Traits\HasQueryGate;
use BehindSolution\LaravelQueryGate\Support\QueryGate;

class Post extends Model
{
    use HasQueryGate;

    public static function queryGate(): QueryGate
    {
        return QueryGate::make()
            ->alias('posts')
            ->filters(['title' => 'string', 'status' => 'string'])
            ->allowedFilters(['title' => ['like'], 'status' => ['eq']])
            ->actions(fn ($a) => $a->create()->update()->delete());
    }
}
Enter fullscreen mode Exit fullscreen mode

Register in Config

// config/query-gate.php
'models' => [
    App\Models\Post::class,
],
Enter fullscreen mode Exit fullscreen mode

Done. You now have a fully functional REST API.

Try the Example Project

Clone the complete example to see everything in action:

git clone git@github.com:behindSolution/LQG-example.git
cd LQG-example
composer install
cp .env.example .env
php artisan key:generate
php artisan migrate --seed
php artisan serve
Enter fullscreen mode Exit fullscreen mode

Import the included Postman collection and start exploring.

Why I Built This

I was tired of writing the same code over and over. Every project needed:

  • Filtering logic
  • Sorting logic
  • Pagination
  • Validation
  • Authorization checks
  • API versioning (if you're lucky)
  • Documentation (if you have time)

Query Gate handles all of this declaratively. You focus on your business logic, not boilerplate.

What's Next?


Have questions or feedback? Drop a comment below or open an issue on GitHub. I'd love to hear how you're using Query Gate in your projects!

If this saved you some time, consider giving the repo a star. It helps more developers discover the project.

Top comments (0)