DEV Community

Cover image for Taking Pagination to the Next Level: Sorting and Filtering in Go APIs
Siddhesh Khandagale
Siddhesh Khandagale

Posted on

Taking Pagination to the Next Level: Sorting and Filtering in Go APIs

In the last post, we tackled Pagination breaking down large API responses into manageable chunks. But if you've ever wanted to let users control how data is sorted or filter specific results, then you're ready for the next step: sorting and filtering.
Let's dive in and make our APIs even more powerful by adding these features.

If you haven't gone through the previous tutorial, here's the link.

Why Sorting and Filtering Matter

Image description
Pagination alone isn't always enough. Imagine a user searching for the newest items or only those created in a specific timeframe. Sorting and filtering let users:

- Sort: Choose the order of results (e.g. newest to oldest)
- Filter: Narrow down results to what they need (e.g. items created today)

By combining pagination with sorting and filtering, we create a more user-friendly API.

Extending Our Database Query

We'll build on the items table from the previous blog. To recap, here's the scheme:

CREATE TABLE items (
    id SERIAL PRIMARY KEY,
    name TEXT NOT NULL,
    created_at TIMESTAMP DEFAULT NOW()
);
Enter fullscreen mode Exit fullscreen mode

Adding Sorting

Sorting is all about ordering the results. We'll allow users to sort by the name or created_at columns in ascending or descending order.

Step 1: Accept Sort Parameters

Users will pass two query parameters:

  • sort : The column to sort by ( name or created_at ).
  • order : The sort direction ( asc or desc ).

Example URL:

/items?page=1&limit=10&sort=created_at&order=desc
Enter fullscreen mode Exit fullscreen mode

Step 2: Adjust the SQL Query

We'll dynamically modify the SQL query to include sorting:

// Extract sort and order query parameters
sort := r.URL.Query().Get("sort")
order := r.URL.Query().Get("order")

// Validate the sort column
validSortColumns := map[string]bool{"name": true, "created_at": true}
if !validSortColumns[sort] {
    sort = "created_at" // Default sort column
}

// Validate the sort order
if order != "asc" && order != "desc" {
    order = "asc" // Default sort order
}

// Modify the SQL query
query := fmt.Sprintf("SELECT id, name, created_at FROM items ORDER BY %s %s LIMIT $1 OFFSET $2", sort, order)
rows, err := db.Query(query, limit, offset)
if err != nil {
    http.Error(w, "Failed to fetch items", http.StatusInternalServerError)
    return
}
Enter fullscreen mode Exit fullscreen mode

Adding Filtering

Filtering lets users refine their search. For example, we can filter items by a date range or items containing a specific keyword in their name.

Step 1: Accept Filter Parameters

We'll support two filters:

  • name : Search for items containing a specific substring.
  • created_after : Fetch items created after a specific date.

Example URL :

/items?page=1&limit=10&name=Item&created_after=2025-01-10 20:38:57.832777
Enter fullscreen mode Exit fullscreen mode

Step 2: Adjust the SQL Query

We'll add WHERE conditions to handle these filters:

// Extract filter query parameters
nameFilter := r.URL.Query().Get("name")
createdAfter := r.URL.Query().Get("created_after")

// Build the WHERE clause dynamically
whereClauses := []string{}
args := []interface{}{}
argIndex := 1

if nameFilter != "" {
    whereClauses = append(whereClauses, "name ILIKE $1")
    args = append(args, "%"+nameFilter+"%")
    argIndex++
}

if createdAfter != "" {
    whereClauses = append(whereClauses, fmt.Sprintf("created_at > $%d", argIndex))
    args = append(args, createdAfter)
    argIndex++
}

args = append(args, limit, offset)

// Combine WHERE clauses
whereSQL := ""
if len(whereClauses) > 0 {
    whereSQL = "WHERE " + strings.Join(whereClauses, " AND ")
}

// Final query
query := fmt.Sprintf("SELECT id, name, created_at FROM items %s ORDER BY %s %s LIMIT $%d OFFSET $%d", whereSQL, sort, order, argIndex, argIndex+1)
rows, err := db.Query(query, args...)
if err != nil {
    http.Error(w, "Failed to fetch items", http.StatusInternalServerError)
    return
}
Enter fullscreen mode Exit fullscreen mode

Testing the Enhanced API

  • Sorting by name in ascending order:
curl "http://localhost:8080/items?page=1&limit=5&sort=name&order=asc"
Enter fullscreen mode Exit fullscreen mode
  • Filtering items created after a specific date:
curl "http://localhost:8080/items?page=1&limit=5&created_after=2025-01-10 20:38:57.832777"
Enter fullscreen mode Exit fullscreen mode
  • Combining filters and sorting:
curl "http://localhost:8080/items?page=1&limit=5&name=Item&sort=created_at&order=desc"
Enter fullscreen mode Exit fullscreen mode

Common Mistake to Avoid

Not validating user input: Allowing arbitrary columns or invalid sort orders can expose your database to SQL injection. Always validate inputs.

Conclusion / Next Steps

You can find the complete code repository for this tutorial here

With pagination, sorting, and filtering in place, your API is now more user-friendly and flexible. For even more advanced functionality, consider adding:

  • Cursor-based pagination for large datasets.
  • Faceted filtering for complex searches.

Stay tuned for the next post where we’ll explore these advanced techniques!

To get more information about Golang concepts, projects, etc. and to stay updated on the Tutorials do follow Siddhesh on Twitter and GitHub.

Until then Keep Learning, Keep Building 🚀🚀

Top comments (0)