Backend Sorting with GET Parameters Instead of JavaScript

📖 3 minutes read

When building sortable lists, the temptation is to handle sorting in JavaScript—intercept clicks, reorder the DOM, maybe use a library like DataTables. But there’s a simpler, more robust approach: let the backend handle it via GET parameters.

The Backend Sorting Pattern

Instead of JavaScript, pass sorting preferences through the URL:

$allowedSorts = ['created_at', 'updated_at', 'name', 'price'];
$sort = request('sort', 'created_at');
$direction = request('direction', 'desc');

if (!in_array($sort, $allowedSorts)) {
    $sort = 'created_at';
}

$items = Item::orderBy($sort, $direction)->paginate(20);

Then in your view, generate sort links:

<select onchange="window.location.href=this.value">
    <option value="?sort=created_at&direction=desc" {{ request('sort') == 'created_at' ? 'selected' : '' }}>
        Newest First
    </option>
    <option value="?sort=name&direction=asc" {{ request('sort') == 'name' ? 'selected' : '' }}>
        Name (A-Z)
    </option>
    <option value="?sort=price&direction=asc" {{ request('sort') == 'price' ? 'selected' : '' }}>
        Price (Low to High)
    </option>
</select>

Why Backend Sorting Wins

State in the URL: Users can bookmark a sorted view, share links, and browser back/forward works correctly. With JS sorting, the URL doesn’t change—the state lives only in memory.

Works with pagination: Client-side sorting breaks when you paginate (you’re only sorting the current page). Backend sorting applies across all records.

No Vue template issues: If you’re using Vue, putting <script> tags in templates causes parsing errors. Backend sorting keeps JavaScript out of your Blade/Vue templates entirely.

RESTful and simple: The URL describes the resource state. It’s the way the web was designed to work.

Whitelist Validation is Critical

Notice the $allowedSorts array? This prevents SQL injection via column names:

if (!in_array($sort, $allowedSorts)) {
    $sort = 'created_at'; // fallback to default
}

Without this check, a malicious user could inject arbitrary SQL by crafting a URL like ?sort=malicious_column. Always validate sort columns against a whitelist.

Handling Relationships

You can even sort by related model columns using joins:

$allowedSorts = ['created_at', 'name', 'category_name'];
$sort = request('sort', 'created_at');

if ($sort === 'category_name') {
    $items = Item::join('categories', 'items.category_id', '=', 'categories.id')
        ->select('items.*')
        ->orderBy('categories.name', $direction)
        ->paginate(20);
} else {
    $items = Item::orderBy($sort, $direction)->paginate(20);
}

Preserving Other Query Parameters

If your page has filters or search, append them to sort links using request()->except():

$queryParams = request()->except('sort', 'direction');
$queryParams['sort'] = 'name';
$queryParams['direction'] = 'asc';

<a href="?{{ http_build_query($queryParams) }}">Sort by Name</a>

Or use Laravel’s appends method on paginated results:

{{ $items->appends(request()->except('page'))->links() }}

When JavaScript Sorting Makes Sense

There are still valid use cases for client-side sorting:

  • Small datasets (< 100 rows) that fit on one page
  • Real-time data that updates frequently via WebSocket
  • Interactive tables where instant response is critical (trading dashboards, etc.)

But for most admin panels and user-facing lists, backend sorting with GET parameters is simpler, more robust, and plays nicely with pagination, bookmarking, and server-side rendering.

Let the URL do the work. It’s already there for a reason.

Daryle De Silva

VP of Technology

11+ years building and scaling web applications. Writing about what I learn in the trenches.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *