API Design and GraphQL Advanced
Patterns
Comprehensive Table of Contents
1. RESTful API Design Principles
2. API Versioning and Evolution
3. Error Handling and Status Codes
4. Rate Limiting and Throttling
5. Authentication (OAuth 2.0, JWT, API Keys)
6. Authorization and Scopes
7. GraphQL Fundamentals and Query Language
8. GraphQL Schema Design
9. GraphQL Performance and N+1 Problems
10. API Documentation and Developer Experience
11. Caching Strategies for APIs
12. API Monitoring and Analytics
Chapter 1: RESTful API Design
1.1 Core REST Principles
Representational State Transfer:
Client-Server:
├─ Client and server are independent
├─ Can evolve separately
├─ Server provides resources
├─ Client consumes resources
Stateless:
├─ No client context stored on server
├─ Every request contains all needed info
├─ Easier scaling
├─ Simpler implementation
Cacheable:
├─ Responses marked as cacheable or not
├─ Reduces network traffic
├─ Improves performance
├─ CDN friendly
Uniform Interface:
├─ Consistent resource identification
├─ Standard methods (GET, POST, PUT, DELETE)
├─ Self-descriptive messages
├─ HATEOAS (optional)
Resource-Oriented:
├─ Resources are nouns (not verbs)
├─ Resources have unique identifiers (URIs)
├─ Use HTTP methods for operations
├─ Example: /users, /orders, /products
HTTP Methods:
GET:
├─ Retrieve resource
├─ Safe: No side effects
├─ Idempotent: Same result repeated
├─ Cacheable
POST:
├─ Create resource
├─ Not safe
├─ Not idempotent (each call creates new)
├─ Response includes location
PUT:
├─ Replace entire resource
├─ Not safe
├─ Idempotent
├─ 201 Created or 204 No Content
PATCH:
├─ Partial update
├─ Not safe
├─ May not be idempotent
├─ Apply to subset
DELETE:
├─ Remove resource
├─ Not safe
├─ Idempotent
├─ 204 No Content response
1.2 API Design Best Practices
URI Design:
Good:
/users
/users/123
/users/123/orders
/users/123/orders/456
Bad:
/getUsers
/deleteUser?id=123
/user/get/123
/GetAllOrders
Filtering, Sorting, Pagination:
Filtering:
├─ /users?status=active&role=admin
├─ Query parameters
├─ Server-side filtering
├─ Multiple filters AND'ed
Sorting:
├─ /users?sort=name,-created_at
├─ Minus sign for descending
├─ Multiple fields possible
├─ Default sorting specified
Pagination:
├─ /users?limit=10&offset=0
├─ or /users?page=1&per_page=10
├─ Cursor-based for large datasets
├─ Include total count in response
Content Negotiation:
Accept Header:
├─ Client specifies format
├─ Accept: application/json
├─ Accept: application/xml
├─ Accept: text/csv
Content-Type Header:
├─ Server specifies format
├─ Content-Type: application/json
├─ Content-Type: application/xml
Versioning:
URL Versioning:
├─ /v1/users
├─ /v2/users
├─ Clear but URI clutter
Header Versioning:
├─ Accept: application/[Link].v1+json
├─ Cleaner URIs
├─ Less discoverable
Query Parameter:
├─ /users?version=1
├─ Flexible but confusing
Recommendation:
├─ URL versioning for major changes
├─ Deprecation strategy
├─ Sunset header
├─ Timeline for old versions
Chapter 2: GraphQL Fundamentals
2.1 GraphQL Schema Design
Core Concepts:
Schema:
├─ Type system definition
├─ What queries are possible
├─ What data available
├─ What mutations allowed
Types:
Scalar Types:
├─ String, Int, Float, Boolean, ID
├─ Leaf values
├─ Non-null (!) and list ([])
Object Types:
├─ Named collection of fields
├─ Example: User { id, name, email }
├─ Can reference other types
Interfaces:
├─ Define contract
├─ Multiple types implement interface
├─ Example: Node interface { id }
Unions:
├─ Either-or types
├─ Example: SearchResult = User | Post | Comment
├─ Useful for flexible returns
Example Schema:
```graphql
type User {
id: ID!
name: String!
email: String!
posts: [Post!]!
createdAt: String!
}
type Post {
id: ID!
title: String!
content: String!
author: User!
comments: [Comment!]!
}
type Comment {
id: ID!
text: String!
author: User!
post: Post!
}
type Query {
user(id: ID!): User
users(limit: Int, offset: Int): [User!]!
post(id: ID!): Post
posts(authorId: ID!): [Post!]!
}
type Mutation {
createUser(name: String!, email: String!): User!
updateUser(id: ID!, name: String): User
deleteUser(id: ID!): Boolean!
createPost(title: String!, content: String!): Post!
}
Query Examples:
query GetUser {
user(id: "123") {
id
name
email
posts {
id
title
}
}
}
query GetPosts {
posts(authorId: "123") {
id
title
author {
name
}
comments {
text
author {
name
}
}
}
}
### 2.2 Performance and Best Practices
N+1 Query Problem:
Problem:
query GetUsers {
users(limit: 10) {
id
name
posts { # This causes N queries!
id
title
}
}
}
// 1 query for users
// N queries for posts (1 per user)
// Total: N+1 queries
Solutions:
DataLoader: ├─ Batch similar queries ├─ Cache within request ├─ Single query for all
posts ├─ Example: DataLoader(batchFunc)
Query Optimization: ├─ Limit depth ├─ Limit complexity score ├─ Set timeouts ├─
Cache results
Field Resolver Optimization:
// Inefficient: Each field queries database
const userResolvers = {
posts: (user) => [Link]([Link]) // N queries
}
// Efficient: Batch loading
const userResolvers = {
posts: (user, args, context) =>
[Link]([Link])
}
Caching:
Query Caching: ├─ Cache full query results ├─ TTL-based expiration ├─ Invalidate on
mutation ├─ Example: Redis cache
Field-level Caching: ├─ Cache individual field results ├─ Granular control ├─ More
complex invalidation
Pagination:
Offset-based:
query {
users(limit: 10, offset: 20) {
id
name
}
}
Cursor-based:
query {
users(first: 10, after: "cursor123") {
edges {
node {
id
name
}
cursor
}
pageInfo {
hasNextPage
endCursor
}
}
}
Error Handling:
Example:
mutation CreateUser {
createUser(email: "invalid") {
user {
id
}
errors {
message
code
path
}
}
}
Best Practices: ├─ Consistent error format ├─ Helpful error messages ├─ Include error
codes ├─ Include error path ├─ HTTP status codes still matter
---
## Chapter 3: Authentication and Authorization
### 3.1 OAuth 2.0 Flow
Roles:
Resource Owner: ├─ User who owns the data ├─ You (the person)
Resource Server: ├─ Hosts protected resources ├─ Validates tokens ├─ Example: API
server
Authorization Server: ├─ Issues tokens ├─ Verifies credentials ├─ Example: OAuth
provider
Client: ├─ Application accessing resource ├─ Example: Mobile app, web app
Authorization Code Flow:
1. User clicks “Login with Google”
2. Browser redirects to: [Link]
3. User logs in and grants permission
4. Google redirects to: [Link]
5. Backend exchanges code for token
6. Backend receives access_token
7. Backend uses token to access user data
8. Backend creates session
Implicit Flow (Legacy): ├─ Token directly in redirect ├─ No backend-to-backend
exchange ├─ Less secure ├─ Deprecated
Client Credentials Flow: ├─ Service-to-service ├─ No user involved ├─ Direct token
request ├─ Example: Cron jobs, bots
Refresh Token Flow: ├─ Refresh token has long expiration ├─ Access token has short
expiration ├─ Refresh to get new access token ├─ Access token revocation only affects
that token
### 3.2 JWT and API Keys
JWT (JSON Web Tokens):
Structure: [Link]
Example: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIn0.
TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
Header: └─ Algorithm, token type
Payload (Claims): ├─ sub (subject) ├─ iat (issued at) ├─ exp (expiration) ├─ Custom
claims: user_id, role, etc.
Signature: ├─ Verify token authenticity ├─ Base64(algorithm([Link], secret)) ├─
Prevents tampering
Benefits: ├─ Self-contained ├─ Stateless ├─ Cacheable ├─ Mobile-friendly
Drawbacks: ├─ Can’t revoke immediately ├─ Payload is visible (not encrypted) ├─
Token size larger
API Keys:
Simple approach: ├─ Long random string ├─ Client sends in header: Authorization: Bearer
key ├─ Server validates against database ├─ Simple but less secure
Rate Limit by API Key: ├─ Different limits per key ├─ Track usage ├─ Revoke individual
keys
Scopes:
OAuth Scopes: ├─ read:users ├─ write:posts ├─ delete:comments ├─ Principle of least
privilege
Example Request: ├─ Authorization: Bearer eyJhbGc… ├─ Scope included in token ├─
API verifies scope for operation
Security Best Practices: ├─ HTTPS only ├─ Rotate keys regularly ├─ Use short
expiration times ├─ Refresh tokens for new access ├─ Invalidate on logout ├─ Hash
tokens in database ├─ Use secure storage on client ```
Chapters 4-12 (Abbreviated)
[Continued sections on API Versioning, Error Handling, Rate Limiting, Authorization,
Documentation, Caching, and Monitoring - maintaining same detailed technical pattern]
Conclusion
Good API design enables developers to build amazing applications. Both REST and
GraphQL have their place, and understanding the trade-offs helps choose the right tool.
Key takeaways: - REST: Simpler, cacheable, well-understood - GraphQL: Flexible, precise,
powerful - Versioning: Plan for evolution - Error handling: Helpful and consistent -
Authentication: OAuth 2.0 is standard - Rate limiting: Protect your API - Documentation:
Essential for adoption - Performance: Monitor and optimize - Security: HTTPS, validate,
sanitize - Pagination: Handle large datasets - Filtering: Let clients specify what they need -
Developer experience: It matters
APIs are the interface between your service and the world. Make them good.