Designing Useful APIs: Best Practices for Naming, Idempotency, Pagination, Filtering, Versioning & Security
Best Practices in API Design (A Practical Guide)
APIs are the connective tissue of modern software. A useful API is predictable, consistent, and safe to evolve. Below is a concise, hands-on guide—mirroring the structure in the image—covering eight fundamentals with patterns, anti-patterns, and snippets you can drop into your docs.
1) Use Clear Naming
Principles
- Nouns, not verbs for resources:
/products
,/orders/123
,/users/42
. - Plural collections; singular items:
/products
(list/create),/products/{id}
(read/update/delete). - Use subresources for relationships/actions:
- Relationship:
/orders/123/items
- Domain actions (state changes) as subpaths:
/orders/123/cancel
,/users/42/verify
- Relationship:
- Consistent casing (kebab-case or snake_case) and predictable errors.
Do
POST /api/v1/products
GET /api/v1/products?category=shoes
PATCH /api/v1/products/123
DELETE /api/v1/products/123
Don’t
/createNewProduct
/getProductsList
/Products/GetById?id=123
2) Idempotency
Some HTTP methods are inherently idempotent (GET, HEAD, PUT, DELETE). POST usually isn’t—so make it behave idempotently where duplicates would hurt (e.g., payments, orders) using an idempotency key.
Pattern
- Client generates a unique key and sends it with the request.
- Server stores the result (or hash) keyed by that token for a time window.
- Replays with the same key return the original response.
Example
curl -X POST https://api.example.com/v1/orders -H "Idempotency-Key: 7f9a2b3c-1e4d-4b36-b8b1-6d7a3caa9b7e" -H "Content-Type: application/json" -d '{"cart_id":"c_123","payment_method":"pm_987"}'
Tips
- Return 409 Conflict or 412 Precondition Failed for conflicting replays.
- Make keys opaque to the server and unique per operation.
3) Pagination
Prevent payload bloat and hotspot queries by paging your lists. Two common strategies:
Offset-based
- Simple mental model:
?limit=50&offset=100
- Works well for small data sets.
- Can be inaccurate if rows are inserted/deleted rapidly.
GET /v1/products?limit=25&offset=50&sort=created_at
Cursor-based
- Uses a stable pointer (e.g., last item’s sort key).
- Reliable under heavy writes; recommended for large or real-time feeds.
GET /v1/products?limit=25&cursor=eyJpZCI6IjEyMyIsImNyZWF0ZWRfYXQiOiIyMDI1LTA4LTE3In0=
Always include pagination metadata & links
200 OK
Link: <.../products?cursor=abc>; rel="next",
<.../products?cursor=>; rel="prev"
{
"data": [...],
"page": { "limit": 25, "next_cursor": "abc" }
}
4) Sorting and Filtering
Expose explicit, whitelisted fields; keep syntax predictable.
GET /v1/products
?filter[category]=running
&filter[price_lte]=100
&sort=-created_at,price
&fields= id,name,price,thumbnail_url
Notes
- Use
-field
to indicate descending. - Support compound sorts and typed filters (e.g.,
_gte
,_lte
,_in
). - Validate unknown filters and reject with 400 to prevent silent surprises.
5) Cross-Resource References
Prefer resource paths over long query strings when referencing related entities:
Prefer
/api/v1/carts/123/items/321
Avoid (harder to read/cache)
/api/v1/items?cart_id=123&item_id=321
When creating dependent resources, return 201 Created with a Location header to the canonical URL:
Location: /api/v1/carts/123/items/321
Also consider including link objects in payloads:
{
"id": "321",
"sku": "SKU-1",
"links": { "cart": "/api/v1/carts/123" }
}
6) Rate Limiting
Protect reliability by capping request throughput per token/IP/account. Communicate limits clearly.
Status & headers
- Use 429 Too Many Requests when exceeded.
- Include modern rate limit headers (RFC 9332 style):
RateLimit-Limit: 100;w=3600
RateLimit-Remaining: 42
RateLimit-Reset: 1723872000
Retry-After: 120
Good practices
- Different buckets by endpoint, user, org, or write vs read.
- Offer exponential backoff guidance in docs.
- Log & alert on sustained 429s.
7) Versioning
You need versioning only for breaking changes. Prefer backwards-compatible evolution (add fields, not remove/rename).
Common strategies
- URI versioning (simple, explicit)
/api/v1/users /api/v2/users
- Media type (Accept header)
Accept: application/vnd.example.v2+json
Deprecation workflow
- Announce in changelog.
- Send
Deprecation: true
and Sunset headers on old endpoints. - Provide a migration guide and dual-serve for a grace period.
8) Security
Security is non-negotiable. Apply defense-in-depth:
Transport & boundaries
- TLS everywhere (HTTPS only). Redirect and HSTS.
- Validate content types; enforce sane payload sizes.
AuthN / AuthZ
- Support API keys for server-to-server use; OAuth2/OIDC for user-delegated flows; JWT for stateless sessions.
- Use scopes/roles with least privilege (e.g.,
products:read
,orders:write
). - Rotate keys, set short JWT expirations, and prefer PKCE for public clients.
Example
Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...
Input & data handling
- Validate and sanitize inputs (JSON Schema).
- Consistent error shapes; avoid leaking internals.
- Store secrets in a vault; never in code or logs.
- Audit logging for sensitive actions; enable request tracing.
- Protect against replay (nonces, idempotency keys), injection (parameterized queries), and object-level auth (check ownership).
Quick Checklist
Location
set on 201s.
Reference Snippets (copy/paste)
Standard response envelope
{
"data": {...},
"errors": [],
"meta": { "request_id": "req_abc123", "trace_id": "00-..." },
"links": { "self": "/v1/products/123" }
}
Error shape
{
"errors": [
{
"code": "validation_failed",
"title": "Invalid filter",
"detail": "Unknown filter 'colour'",
"source": { "parameter": "filter[colour]" }
}
]
}
CORS (sane defaults)
Access-Control-Allow-Origin: https://your.app
Access-Control-Allow-Methods: GET,POST,PATCH,DELETE,OPTIONS
Access-Control-Allow-Headers: Authorization,Content-Type,Idempotency-Key
Final Thoughts
Designing useful APIs boils down to clarity, safety, and evolvability. If you implement the eight pillars above—and document them with real examples—your API will be predictable for consumers today and resilient to change tomorrow.
Comments
Post a Comment