php 110 lines · 4 tabs

Throttled Last-Seen Tracking with Laravel Middleware and Redis Cache

Shared by codesnips Aug 2026
4 tabs
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    public function up(): void
    {
        Schema::table('users', function (Blueprint $table) {
            $table->timestamp('last_seen_at')->nullable()->index()->after('remember_token');
        });
    }

    public function down(): void
    {
        Schema::table('users', function (Blueprint $table) {
            $table->dropColumn('last_seen_at');
        });
    }
};
4 files · php Explain with highlit

Recording when a user was last active sounds trivial — update a last_seen_at column on every request — but doing it naively writes to the database on every authenticated hit, which turns a cheap read-heavy app into a write-heavy one. This snippet shows the common production compromise: track activity in middleware, but throttle the actual database write so it happens at most once per interval per user.

The 2024_add_last_seen_to_users migration adds a nullable last_seen_at timestamp and indexes it, since presence queries ("who was online in the last 5 minutes") filter on that column. Keeping it nullable matters because existing rows and never-logged-in accounts legitimately have no value.

UpdateLastSeen middleware is the heart of the pattern. It runs after resolving the authenticated user via Auth::user(), then uses Cache::add() as a lightweight distributed lock: add only writes the key if it does not already exist and returns false otherwise, so within the THROTTLE_SECONDS window the middleware short-circuits and skips the write entirely. This collapses hundreds of requests into a single UPDATE. The middleware also defers work to $next($request)->send-adjacent flow by touching the timestamp before returning the response, and it swallows the case of guest requests by returning early when $user is null. Writing through forceFill() plus saveQuietly() avoids firing model events and updated_at churn that unrelated observers might react to.

User model exposes the domain concept rather than raw column access: scopeOnline() builds the "active within N minutes" query using where('last_seen_at', '>=', ...), and isOnline() answers the boolean for a single record. Centralizing the threshold here keeps the definition of "online" in one place.

The trade-off is deliberate: last-seen becomes eventually consistent and coarse-grained (accurate to within the throttle window), which is perfectly acceptable for presence UI. The Redis-backed cache guard means the throttle works correctly across multiple app servers, unlike an in-process static flag. A pitfall to watch is cache eviction — if the cache is flushed, writes resume immediately, which is safe but slightly chattier. Registering the middleware in the web group ensures it only runs for session-authenticated traffic.


Related snips

Share this code

Here's the card — post it anywhere.

Throttled Last-Seen Tracking with Laravel Middleware and Redis Cache — share card
Link copied