Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

kajol's avatar

kajol started a new conversation+100 XP

5mos ago

Over the past few years I’ve been building Android apps paired with Laravel backends, and I kept running into the same problems:

  • breaking older app versions because I changed an API
  • inconsistent response formats
  • heavy logic inside controllers causing UI delays
  • background tasks slowing down mobile requests
  • auth flows that worked in Postman but failed on real devices
  • rate limiting misconfigured for mobile patterns
  • multi-tenant data mixing because of small query mistakes

After making enough painful mistakes, I standardized the way I structure Laravel APIs specifically for mobile clients. Sharing it here in case it helps someone else avoid the same issues.

  1. API Route Structure for Mobile Clients I always separate mobile API routes in routes/api.php using a clear version + prefix:

Route::prefix('api/v1')->group(function () {

// Auth
Route::post('/login', [AuthController::class, 'login']);
Route::post('/logout', [AuthController::class, 'logout']);

// User
Route::middleware('auth:sanctum')->group(function () {
    Route::get('/me', [UserController::class, 'me']);
    Route::put('/profile', [UserController::class, 'update']);
});

// App-specific endpoints
Route::get('/items', [ItemController::class, 'index']);
Route::post('/items', [ItemController::class, 'store']);

});

Why this matters for Android apps:

  • Every app version in the wild hits a stable contract
  • If you change something later, v1 keeps working
  • Android clients rely heavily on predictable JSON structure

Consistent JSON Response Standardizing this early saves you from debugging UI crashes later.

I normally return this format: return response()->json([ 'status' => 'success', 'data' => $payload, 'errors' => null ], 200);

Or for errors: return response()->json([ 'status' => 'error', 'data' => null, 'errors' => ['Invalid credentials.'] ], 422);

  1. API Versioning (v1, v2, v3…) Without Breaking Old Android Apps Android apps don’t self-update instantly. You must assume old versions will stay alive for months (sometimes years).

So I NEVER modify a live API contract. If something needs to change, I create a new version: // api/v2 Route::prefix('api/v2')->group(function () { Route::get('/profile', [V2\ProfileController::class, 'show']); });

When to create a new version:

  • changing response structure
  • adding/removing fields
  • introducing new validation rules
  • modifying business logic that the mobile app depends on
  • pagination format changes

When NOT to version:

  • adding optional fields
  • adding new endpoints
  • fixing bugs inside existing endpoints

Mobile apps break easily if you don’t respect version boundaries. Versioning is the cheapest insurance you’ll ever buy.

  1. Auth Setup (Sanctum for mobile) I’ve tried Passport and JWT, but Sanctum hits the sweet spot for mobile apps:
  • lighter than Passport
  • simpler token issuing
  • works perfectly with API tokens
  • great DX for Android clients

Login Endpoint public function login(Request $request) { $credentials = $request->validate([ 'email' => 'required|email', 'password' => 'required' ]);

if (!Auth::attempt($credentials)) {
    return response()->json(['status' => 'error', 'errors' => ['Invalid credentials']], 401);
}

$user = Auth::user();
$token = $user->createToken('android')->plainTextToken;

return response()->json([
    'status' => 'success',
    'data' => [
        'token' => $token,
        'user'  => $user
    ],
    'errors' => null
]);

}

Authenticated Routes Route::middleware('auth:sanctum')->group(function () { Route::get('/me', [UserController::class, 'me']); });

Android Client Pattern On Android:

  • Save token securely
  • Send header: Authorization: Bearer
  • When you get 401 → try refresh logic → logout if needed
  1. Offloading Heavy Work Using Queues (Critical for Mobile Performance) Mobile clients hate slow responses. If your API stalls, the Android UI stalls. ANRs can appear when slow endpoints force the client to block.

Rule I follow: Never send email, notifications, or heavy logic in the request lifecycle.

Example Job class SendWelcomeNotification implements ShouldQueue { public function __construct(User $user) { $this->user = $user; }

public function handle()
{
    // Push notification logic
    Notification::send($this->user, new WelcomeNotif());
}

}

Dispatch from Controller dispatch(new SendWelcomeNotification($user));

Why mobile benefits:

  • API responds instantly
  • User flow becomes smooth
  • No timeouts
  • Less risk of retry storms on the client

If you use Horizon + Redis, even better stability.

  1. Rate Limiting for Mobile Clients Mobile behaviors = bursts:
  • user taps fast
  • offline→ online
  • retry logic
  • preloading screens Too-strict throttling breaks Android apps.

I typically use: Route::middleware('throttle:60,1')->group(function () { // API routes });

Meaning: 60 requests per minute per IP/token

For authenticated routes, consider token-based limiting.

You can tune based on:

  • startup sequence
  • how many API calls first screen loads
  • retry patterns
  • push token registration patterns
  1. Database Structure for Multi-Tenant / B2B Mobile Apps Mobile B2B apps often require tenants (organizations). I use a SINGLE database with tenant scoping in 90% of cases.

Schema Example: users id tenant_id name email

items id tenant_id name ...

Query Scoping $items = Item::where('tenant_id', auth()->user()->tenant_id)->get();

Common mistakes:

  • forgetting to scope queries → data leaks
  • joining tables without tenant filtering
  • seeding tables without tenant_id
  • exposing IDs of another tenant in API If you do multi-tenant + mobile, test tenants aggressively.
  1. A Few Production Lessons That Saved Me These are small but important: ♦ Always include app_version and device_info in API headers Helps diagnose bugs. ♦ Log every failed request with user + tenant Makes debugging easier. ♦ Keep response payloads small Mobile networks choke on large JSON. ♦ Cache aggressively Even small collections cached for 30 seconds make the app feel snappier. ♦ Make all breaking changes opt-in via new API versions Never update existing contracts.

Conclusion Laravel is a fantastic backend for Android apps — if you design the APIs, auth, versioning, queues, and multi-tenant logic with mobile constraints in mind.

This structure has saved me from:

  • breaking app versions in production
  • UI freezes caused by slow endpoints
  • auth issues specific to mobile tokens
  • data leaks in multi-tenant setups
  • inconsistent responses across screens

Hope this helps someone building their next mobile backend. If you have a different approach or improvements, I’d love to hear them.