4 min read
Building Robust RESTful APIs for Mobile Apps With Laravel and PostgreSQL

At iLab Africa, I spend as much time on backend APIs as I do on Android UIs. Over the past year, Laravel has become my default choice for building REST APIs that power mobile applications. I want to write down the patterns that are working for me right now, mostly so I don’t forget them when the next project starts.

Why Laravel for Mobile Backends

  • Rapid scaffolding: Artisan commands generate controllers, models, and migrations in seconds.
  • Eloquent ORM: Database queries stay readable without turning into SQL spaghetti.
  • Routing and middleware: Auth middleware applies to clean REST routes in one line.
  • Ecosystem: Tools like Passport give you a full OAuth2 implementation without writing it from scratch.

Designing APIs for Mobile Constraints

The work I’ve been doing at iLab Africa has made me think about mobile network constraints much more than I expected. Apps that feel fine on a fast laptop connection fall apart when a user is on 3G during a commute.

1. Versioning from Day One

I always prefix routes now. When a breaking change comes in v2, v1 keeps working for older app installs still in the wild.

Route::prefix('v1')->group(function () {
    Route::get('/tasks', [TaskController::class, 'index']);
});

2. Mandatory Pagination

I never return unbounded lists. Mobile clients can’t handle thousands of records at once, and the network cost is real.

public function index(Request $request)
{
    return Task::where('user_id', $request->user()->id)
        ->orderBy('updated_at', 'desc')
        ->paginate(20);
}

3. Compact JSON Responses

Stripping out unnecessary fields matters on 2G or 3G. Every byte adds up.

return response()->json([
    'id' => $task->id,
    'title' => $task->title,
    'updated_at' => $task->updated_at->timestamp
]);

PostgreSQL as the Backing Store

I’ve been choosing PostgreSQL over MySQL for a few features that genuinely save time:

  • JSONB columns: Store flexible metadata without extra tables.
  • Better concurrency: Handles simultaneous writes more gracefully.
  • Full-text search: Built-in support for simple search use cases without an external service.

Authentication and Security

Token-based auth is my standard. I use Laravel Passport for OAuth2 flows and simple custom tokens for lighter needs.

// Protecting routes
Route::middleware('auth:api')->group(function () {
    Route::get('/tasks', [TaskController::class, 'index']);
});

Security Checklist:

  • HTTPS via Let’s Encrypt.
  • Rate limiting using Laravel’s throttle middleware.
  • Input validation via FormRequest classes.
  • SQL injection protection via Eloquent.

Monitoring and Debugging

Debugging API issues reported by mobile users is frustrating when you’re flying blind. My current setup uses structured logging and custom middleware that records request/response pairs to a local log file. For background jobs like sending emails or processing uploads, Horizon gives me visibility into queue throughput and failures - I can see exactly what’s queued, what’s running, and what’s failed.

Real-World Example: Sync Endpoint

This simplified endpoint accepts a batch of tasks from an offline-first mobile app and handles conflicts.

public function sync(Request $request)
{
    $validated = $request->validate([
        'tasks' => 'required|array',
        'tasks.*.id' => 'required|string',
        'tasks.*.title' => 'required|string'
    ]);

    $user = $request->user();
    $synced = [];

    foreach ($validated['tasks'] as $taskData) {
        $task = Task::updateOrCreate(
            ['id' => $taskData['id'], 'user_id' => $user->id],
            ['title' => $taskData['title']]
        );
        $synced[] = $task->id;
    }

    return response()->json(['synced' => $synced, 'server_time' => now()->timestamp]);
}

This stack is working because it’s productive and reliable in contexts where connectivity is a constant challenge. I’m shipping faster than I was a year ago and the apps are behaving better in the field.