DEV Community

Cover image for Build Complete REST APIs in Minutes
Jefferson Silva
Jefferson Silva

Posted on

Build Complete REST APIs in Minutes

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)