Skip to main content

API Design ๐Ÿ”Œ

API design is the process of defining the contract between a server and its clients โ€” the endpoints, data formats, error handling, and interaction patterns that make an API intuitive, consistent, and maintainable.

A well-designed API is self-documenting: the URL structure, HTTP methods, and response codes tell the story.


REST (Representational State Transfer)โ€‹

REST is an architectural style for distributed hypermedia systems. It defines a set of constraints that, when followed, produce APIs that are scalable, stateless, and cacheable.

Core Principlesโ€‹

PrincipleDescription
StatelessEach request contains all information the server needs โ€” no server-side session
Resource-basedURLs represent resources (nouns), not actions (verbs)
Uniform interfaceStandard HTTP methods: GET, POST, PUT, PATCH, DELETE
RepresentationResources can have multiple representations (JSON, XML, HTML)
HATEOASHypermedia as the Engine of Application State โ€” responses include links to related resources

HTTP Methods & Status Codesโ€‹

MethodCRUDIdempotent?Safe?
GETReadโœ…โœ…
POSTCreateโŒโŒ
PUTUpdate (full)โœ…โŒ
PATCHUpdate (partial)โŒโŒ
DELETEDeleteโœ…โŒ
HEADMetadata onlyโœ…โœ…
OPTIONSSupported methodsโœ…โœ…

Common status codes:

CodeMeaningWhen to use
200 OKSuccessGET, PUT, PATCH success
201 CreatedResource createdPOST success (include Location header)
204 No ContentSuccess, no bodyDELETE success
301 Moved PermanentlyURL changedResource moved to new canonical URL
304 Not ModifiedNot modifiedCaching โ€” use with ETag/If-None-Match
400 Bad RequestClient errorInvalid input, malformed JSON
401 UnauthorizedAuthentication requiredMissing/invalid credentials
403 ForbiddenNot allowedAuthenticated but insufficient permissions
404 Not FoundResource not foundSingle resource or collection
409 ConflictState conflictDuplicate resource, version mismatch
422 Unprocessable EntityValidation failureSemantic errors (well-formed, but invalid)
429 Too Many RequestsRate limitedInclude Retry-After header
500 Internal Server ErrorUnexpected errorUnhandled exception (never expose stack trace)
503 Service UnavailableTemporary outageMaintenance, overload โ€” include Retry-After

REST Design Patternsโ€‹

Nested resources (be careful with depth โ€” max 2โ€“3 levels):

GET /users/:id/orders
GET /users/:id/orders/:orderId
POST /users/:id/orders

Pagination:

// Request
GET /api/users?page=2&limit=20&sort=createdAt:desc

// Response
{
"data": [...],
"meta": {
"page": 2,
"limit": 20,
"total": 150,
"totalPages": 8
},
"links": {
"self": "/api/users?page=2&limit=20",
"first": "/api/users?page=1&limit=20",
"prev": "/api/users?page=1&limit=20",
"next": "/api/users?page=3&limit=20",
"last": "/api/users?page=8&limit=20"
}
}

Filtering, searching, sorting:

GET /api/users?status=active&role=admin
GET /api/users?q=john // full-text search
GET /api/users?fields=id,name,email // sparse fieldsets

Consistent error format:

{
"error": {
"code": "VALIDATION_ERROR",
"message": "One or more validation errors occurred",
"details": [
{ "field": "email", "message": "Must be a valid email address" },
{ "field": "age", "message": "Must be >= 18" }
],
"requestId": "req_abc123"
}
}

Versioning Strategiesโ€‹

StrategyExampleProsCons
URL path/api/v1/usersExplicit, easy to routeURL pollution
Query param/api/users?version=1Clean URLsCaching issues
Custom headerAccept: application/vnd.api.v1+jsonClean URLs, flexibleHarder to test (curl, browser)
Content negotiationAccept: application/json; version=1Most RESTfulComplex, tooling support limited

Recommendation: URL path versioning for public APIs (simplest for consumers).


GraphQL ๐Ÿงฌโ€‹

GraphQL is a query language and runtime for APIs. Unlike REST's multiple endpoints, GraphQL exposes a single endpoint. Clients specify exactly which fields they need โ€” eliminating over-fetching and under-fetching.

Core Conceptsโ€‹

Schema โ€” defines types and their relationships:

type User {
id: ID!
name: String!
email: String!
posts: [Post!]!
}

type Post {
id: ID!
title: String!
content: String!
author: User!
}

type Query {
user(id: ID!): User
posts(limit: Int): [Post!]!
}

type Mutation {
createUser(name: String!, email: String!): User!
createPost(title: String!, content: String!, authorId: ID!): Post!
}

Queries โ€” read data (client specifies shape):

# Client request โ€” only these fields will be returned
query {
posts(limit: 5) {
title
author {
name
email
}
}
}

Mutations โ€” write/update data:

mutation {
createUser(name: "Alice", email: "alice@example.com") {
id
name
}
}

Subscriptions โ€” real-time updates over WebSocket:

subscription {
postCreated {
title
author {
name
}
}
}

Resolvers โ€” functions that resolve each field:

const resolvers = {
Query: {
posts: () => db.posts.findAll(),
},
User: {
posts: (user) => db.posts.findByAuthorId(user.id),
},
Mutation: {
createPost: (_, { title, content, authorId }) => {
return db.posts.create({ title, content, authorId });
},
},
};

GraphQL vs RESTโ€‹

CriteriaRESTGraphQL
Data fetchingMultiple endpoints, fixed responsesSingle endpoint, client-specified fields
Over-fetchingCommon โ€” getting more data than neededNone โ€” client requests exact fields
Under-fetchingCommon โ€” multiple round-trips for related dataNone โ€” nested resources in one request
VersioningRequired over timeNo versioning โ€” schema evolves via deprecation
CachingHTTP caching (CDN, browser)Requires client-side caches (Apollo, Urql, Relay)
ToolingSwagger, Postman, curlGraphiQL, Apollo Studio, GraphQL Playground
Learning curveLowMedium โ€” requires understanding schema and queries
Best forSimple CRUD APIs, public APIsComplex data models, mobile apps, rapid UIs

Anti-Patterns to Avoidโ€‹

  • Deeply nested queries โ€” can cause N+1 resolver problems (use DataLoader for batching)
  • Exposing raw database types โ€” create a domain-specific schema, not a mirror of your DB
  • Mutations that aren't verbs โ€” use imperative names: createUser, not userCreate
  • Stringly-typed fields โ€” use enums, unions, and interfaces for type safety
  • No pagination โ€” every list field should be paginated to prevent unbounded queries

API Security (Common to Both REST & GraphQL)โ€‹

  • Always use HTTPS โ€” encrypt data in transit
  • Authenticate every request โ€” JWT Bearer token, API key, or OAuth2
  • Authorize at the resource level โ€” never trust client-provided IDs without verification
  • Validate all input โ€” schema validation before processing
  • Rate limit โ€” prevent abuse (token bucket, sliding window)
  • Set CORS headers explicitly โ€” never use Access-Control-Allow-Origin: * with credentials
  • Log and monitor โ€” track unusual patterns (sudden error spikes, large payloads)

โ† Back to Backend Engineering ยท ยฉ sparshjaswal