DEV Community

Cover image for API Design — Advanced
Gouranga Das Samrat
Gouranga Das Samrat

Posted on

API Design — Advanced

You know the basics. Here's what separates good API design from great API design.


📄 Pagination Strategies

Never return unbounded lists. Always paginate.

1. Offset-Based Pagination

GET /posts?limit=20&offset=40
Enter fullscreen mode Exit fullscreen mode
  • Simple, easy to implement
  • ✅ Jump to any page directly ("go to page 5")
  • ❌ Page drift: if items are added/deleted between requests, you miss or duplicate items
  • ❌ Performance degrades at high offsets (OFFSET 100000 scans 100k rows and discards them)
  • Use when: small datasets, admin panels, search results

2. Cursor-Based Pagination (Recommended for feeds)

GET /posts?limit=20&cursor=eyJpZCI6MTIzfQ==
Response: { data: [...], nextCursor: "eyJpZCI6MTQ0fQ==" }
Enter fullscreen mode Exit fullscreen mode
  • Cursor = encoded pointer to last item seen (e.g., base64 of {id: 123})
  • ✅ Stable — no page drift even if new items inserted
  • ✅ Efficient — WHERE id > 123 LIMIT 20 uses index perfectly
  • ❌ Can't jump to arbitrary pages
  • Use when: infinite scroll, Twitter/Instagram feeds, real-time data

3. Keyset Pagination

  • Variant of cursor — uses actual column values (not encoded)
GET /posts?limit=20&after_id=123&after_created_at=2024-01-15
Enter fullscreen mode Exit fullscreen mode
  • Multi-column sort support
  • Works naturally with DB indexes

4. Page-Based (Simple)

GET /posts?page=3&per_page=20
Enter fullscreen mode Exit fullscreen mode
  • Like offset but friendlier naming
  • Same problems as offset

🔍 Filtering & Sorting

Filtering Patterns

# Simple equality
GET /users?status=active&role=admin

# Range filters
GET /orders?created_after=2024-01-01&created_before=2024-12-31
GET /products?price_min=10&price_max=100

# Multi-value
GET /products?category=electronics,phones

# Search
GET /users?search=john
Enter fullscreen mode Exit fullscreen mode

DB implications:

  • Each filter = potential WHERE clause
  • Ensure composite indexes match common filter combinations
  • status + role both filtered → composite index (status, role)

Sorting

GET /products?sort=price&order=asc
GET /products?sort=-created_at          # prefix - for desc
GET /products?sort=price,-created_at    # multi-sort
Enter fullscreen mode Exit fullscreen mode
  • Sort key must be indexed (or full table scan)
  • For cursor pagination: sort field must be part of the cursor

📊 GraphQL Basics

REST: client gets what the server decides to send.
GraphQL: client asks for exactly what it needs.

# REST: GET /users/123 returns ALL user fields
# GraphQL: client specifies exactly what it wants

query {
  user(id: "123") {
    name
    email
    posts(limit: 3) {
      title
      createdAt
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Problems GraphQL solves:

  • Over-fetching: REST returns 50 fields, you need 3
  • Under-fetching: Need to call 3 REST endpoints to build one page
  • Type safety: Schema defines exactly what's available

When to use GraphQL:

  • Multiple clients (mobile, web, third-party) with different data needs
  • Rapidly evolving APIs (add fields without breaking clients)
  • Complex, interconnected data (think: social graph)

When to stick with REST:

  • Simple CRUD APIs
  • Public APIs (REST is more universally understood)
  • File uploads, streaming (REST handles these better)
  • Small team — GraphQL has operational overhead

N+1 Problem:

query {
  users {
    posts {
      comments
    }
  }
}
# Without DataLoader: 1 + N + N*M queries 🔥
# With DataLoader: batch loads → 3 queries total ✅
Enter fullscreen mode Exit fullscreen mode

🏷️ API Versioning

You mentioned this — here's the full picture:

URL Versioning (Most common)

GET /v1/users
GET /v2/users
Enter fullscreen mode Exit fullscreen mode
  • ✅ Very explicit, easy to route
  • ❌ URL "should" be a resource, not a version

Header Versioning

GET /users
Accept: application/vnd.myapi.v2+json
Enter fullscreen mode Exit fullscreen mode
  • ✅ Clean URLs
  • ❌ Harder to test (can't just paste URL in browser)

Query Parameter

GET /users?version=2
Enter fullscreen mode Exit fullscreen mode
  • ✅ Easy to test
  • ❌ Optional params = easy to forget → inconsistent behavior

Sunset Policy

  • Announce deprecation date for old versions
  • Return Deprecation and Sunset headers on old endpoints
  • Give clients 6–12 months to migrate

🔒 API Best Practices (Interview Checklist)

Topic Best Practice
Auth Bearer token in Authorization header, not URL
Errors Consistent error format: { error: { code, message, details } }
Rate Limiting Return Retry-After header, 429 status
Idempotency POST with Idempotency-Key header for safe retries
Status Codes Use them correctly: 201 Created, 204 No Content, 409 Conflict
HATEOAS Return links to related resources (optional but impressive)
Compression gzip responses > 1KB

⚖️ Trade-offs

⚖️ REST vs GraphQL at interview:

  • Default to REST for simplicity
  • Propose GraphQL when: multiple client types, complex data graph, bandwidth-sensitive (mobile)

⚖️ Offset vs Cursor pagination:

  • Default to cursor for production feeds
  • Use offset for admin/search (random access needed)

Top comments (0)