Modern API Design: REST, GraphQL, and gRPC in Production
Comprehensive comparison of API design patterns with real-world examples, performance benchmarks, and guidance on choosing the right approach.
Introduction: What Makes a Great API?
APIs are the nervous system of modern software. Well-designed APIs enable rapid development; poorly designed APIs create technical debt that lasts years.
- •Intuitive: Developers understand it without reading docs
- •Consistent: Similar things work similarly
- •Predictable: Behavior matches expectations
- •Flexible: Supports current and future use cases
- •Stable: Backwards compatible, versioned properly
- •Performant: Fast response times, efficient
- 1.Breaking changes without versioning
- 2.Inconsistent naming (getUser vs fetchUserData vs retrieveUserInfo)
- 3.Chatty APIs (100 requests to load one page)
- 4.No rate limiting (enables abuse)
- 5.Poor error messages ("Error 500")
- 6.No pagination (returning 1M records)
- •Stripe: Developer experience first
- •GitHub: Consistent REST + GraphQL for flexibility
- •Twilio: Simple things simple, complex things possible
- •AWS: Explicit over implicit
- 1.Design: OpenAPI spec, review with stakeholders
- 2.Build: Implementation, tests, documentation
- 3.Release: Versioning, changelog, migration guide
- 4.Operate: Monitoring, rate limiting, caching
- 5.Evolve: New features, deprecation, sunsetting
RESTful API Design Patterns
REST is the most common API style. Following conventions makes your API predictable and easy to use.
- •GET: Retrieve resource(s) - IDEMPOTENT, SAFE
- •POST: Create new resource - NOT idempotent
- •PUT: Replace entire resource - IDEMPOTENT
- •PATCH: Partial update - NOT idempotent (usually)
- •DELETE: Remove resource - IDEMPOTENT
Resource Naming Conventions:
- •Good:
- •
/users
(plural, lowercase) - •
/users/123
- •
/users/123/orders
- •
/users/123/orders/456
- •
/getUser
(verb in URL, use GET /users/:id) - •
/user
(singular) - •
/Users
(capital) - •
/users/getUserOrders
(mixed convention)
- •
GET /users?role=admin&status=active
- •
GET /orders?created_after=2025-01-01&limit=100
- •
GET /products?search=laptop&sort=price_asc&page=2
Pagination (Required for lists):
Offset-based (simple, but slow for large offsets):
GET /users?offset=100&limit=50
Response: {
"data": [...],
"pagination": {
"offset": 100,
"limit": 50,
"total": 10000
}
}
Cursor-based (faster, consistent):
GET /users?cursor=eyJpZCI6MTIzfQ&limit=50
Response: {
"data": [...],
"pagination": {
"next_cursor": "eyJpZCI6MTczfQ",
"has_more": true
}
}
- •200 OK: Success (GET, PUT, PATCH)
- •201 Created: Resource created (POST)
- •204 No Content: Success, no body (DELETE)
- •400 Bad Request: Client error (validation failed)
- •401 Unauthorized: Not authenticated
- •403 Forbidden: Authenticated but no permission
- •404 Not Found: Resource doesn't exist
- •429 Too Many Requests: Rate limited
- •500 Internal Server Error: Server error
- •503 Service Unavailable: Temporarily down
Security and Performance
Authentication & Authorization:
- •Pros: Simple, fast
- •Cons: No user context, if leaked = full access
- •Authorization Code flow (web apps)
- •Client Credentials (machine-to-machine)
- •Refresh tokens for long-lived access
- •Self-contained (no DB lookup)
- •Short-lived (15-60 min)
- •Include minimal claims (user_id, roles)
Rate Limiting (Prevent abuse):
- •1000 requests per hour
- •Simple but can burst at window edge
- •More accurate, prevents bursts
- •Slightly more complex
- •Allow bursts up to bucket size
- •Refills at steady rate
Headers to Include:
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 847
X-RateLimit-Reset: 1704067200
Caching Strategies:
1. ETags (Conditional requests):
Request: GET /users/123
If-None-Match: "abc123"
Response: 304 Not Modified (if unchanged)
200 OK + body (if changed)
2. Cache-Control Headers:
Cache-Control: public, max-age=3600 # Cache for 1 hour
Cache-Control: private, no-cache # Don't cache
- •Redis for frequently accessed data
- •Invalidate on writes
- •Cache warming for predictable queries
- •Gzip compression (70-90% size reduction)
- •Pagination (never return >100 items without pagination)
- •Field filtering (
GET /users?fields=id,name
) - •Batch operations (
POST /users/batch
) - •Async for long operations (return 202 Accepted + job ID)
Related Articles
Erasure Coding in Distributed Storage: Mathematics, Implementation, and Trade-offs
Comprehensive exploration of erasure coding techniques, Reed-Solomon codes, storage efficiency, fault tolerance mathematics, and practical implementation in systems like HDFS, Ceph, and S3.
ArchitectureModern Distributed Computing Patterns: From Theory to Practice
Explore distributed systems architecture patterns including MapReduce, actor models, event sourcing, and CQRS with real-world implementation examples.