Knowing RESTWhat is rest?An architectural style for web APIs where URLs represent resources (nouns) and HTTP methods (GET, POST, PUT, DELETE) represent actions on those resources. basics is easy. Building a REST APIWhat is api?A set of rules that lets one program talk to another, usually over the internet, by sending requests and getting responses. that does not become a maintenance nightmare after six months is harder. This lesson covers the patterns that separate well-designed APIs from ones that developers curse at.
PaginationWhat is pagination?Splitting a large set of results into smaller pages so the server and client only handle a manageable chunk at a time.: cursor vs offset
findAll() call that takes 5ms in development takes 30 seconds in production and crashes the response serializer.Any endpointWhat is endpoint?A specific URL path on a server that handles a particular type of request, like GET /api/users. that returns a list must be paginated. Without pagination, your database query returns 50,000 rows, your server serializes them all to JSONWhat is json?A text format for exchanging data between systems. It uses key-value pairs and arrays, and every programming language can read and write it., and your client crashes trying to render them.
Offset-based pagination
The simpler approach. The client says "give me items 20 through 40."
GET /api/articles?offset=20&limit=20{
"data": [...],
"pagination": {
"total": 1523,
"offset": 20,
"limit": 20,
"hasMore": true
}
}Cursor-based paginationWhat is cursor-based pagination?A pagination method that uses an indexed column value from the last seen row rather than OFFSET, staying fast on very large tables.
The client says "give me 20 items after this marker."
GET /api/articles?after=eyJpZCI6NDJ9&limit=20{
"data": [...],
"pagination": {
"endCursor": "eyJpZCI6NjJ9",
"hasNextPage": true
}
}The cursor is an opaque tokenWhat is token?The smallest unit of text an LLM processes - roughly three-quarters of a word. API pricing is based on how many tokens you use. (usually a base64-encoded ID or timestamp) that the server uses to find the next batch.
Which to choose?
| Factor | Offset | Cursor |
|---|---|---|
| Implementation | Simple | More complex |
| "Jump to page 5" | Yes | No |
| Performance at page 1000 | Slow (DB scans offset rows) | Fast (seeks directly) |
| New items inserted | Duplicates or skips items | Stable, no duplicates |
| Deletes during pagination | Skips items | Stable |
| Best for | Admin dashboards, static data | Feeds, real-time data, mobile apps |
Rule of thumb: If users need "page 5 of 20," use offset. If they scroll infinitely through a feed, use cursor.
Filtering patterns
Filtering should be predictable across your entire APIWhat is api?A set of rules that lets one program talk to another, usually over the internet, by sending requests and getting responses.. Pick conventions and stick to them.
GET /api/articles?status=published&author=42&sort=-createdAt&fields=id,title,summaryCommon conventions
// Equality filter
GET /api/users?role=admin
// Multiple values (OR)
GET /api/users?role=admin,editor
// Range filters
GET /api/products?price_min=10&price_max=100
// Date ranges
GET /api/orders?created_after=2024-01-01&created_before=2024-06-01
// Search
GET /api/articles?q=graphql+caching
// Sorting (prefix - for descending)
GET /api/articles?sort=-createdAt,title
// Field selection (sparse fieldsets)
GET /api/users?fields=id,name,email| Convention | Format | Example |
|---|---|---|
| Equality | ?field=value | ?status=active |
| Multiple values | ?field=a,b | ?role=admin,editor |
| Range | ?field_min=x&field_max=y | ?price_min=10&price_max=50 |
| Sort ascending | ?sort=field | ?sort=name |
| Sort descending | ?sort=-field | ?sort=-createdAt |
| Field selection | ?fields=a,b,c | ?fields=id,name |
| Search | ?q=term | ?q=javascript |
| Pagination (offset) | ?offset=N&limit=N | ?offset=20&limit=10 |
| Pagination (cursor) | ?after=cursor&limit=N | ?after=abc123&limit=10 |
HATEOASWhat is hateoas?A REST constraint where API responses include hyperlinks to related actions, so clients can discover available operations without hardcoding URLs.: links in responses
HATEOAS is a RESTWhat is rest?An architectural style for web APIs where URLs represent resources (nouns) and HTTP methods (GET, POST, PUT, DELETE) represent actions on those resources. constraint that says responses should include links to related actions. The idea is that a client can navigate the APIWhat is api?A set of rules that lets one program talk to another, usually over the internet, by sending requests and getting responses. without hardcoding URLs.
{
"id": 42,
"title": "REST Best Practices",
"status": "draft",
"_links": {
"self": { "href": "/api/articles/42" },
"publish": { "href": "/api/articles/42/publish", "method": "POST" },
"author": { "href": "/api/users/7" },
"comments": { "href": "/api/articles/42/comments" }
}
}In practice, full HATEOAS is rare. Most APIs include paginationWhat is pagination?Splitting a large set of results into smaller pages so the server and client only handle a manageable chunk at a time. links (next, prev) but skip the rest. The concept is worth knowing because it shows up in interviews and API design discussions.
Idempotency keys
Network failures happen. When a client sends a POST request to create a payment and the connection drops before receiving the response, should it retry? If it does, will it create a duplicate payment?
Idempotency keys solve this. The client generates a unique key and sends it with the request. If the server receives the same key twice, it returns the original response instead of processing again.
// Client generates a unique key per operation
const idempotencyKey = crypto.randomUUID();
const response = await fetch('/api/payments', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Idempotency-Key': idempotencyKey
},
body: JSON.stringify({
amount: 4999,
currency: 'usd',
customer: 'cus_abc123'
})
});// Server-side implementation
app.post('/api/payments', async (req, res) => {
const idempotencyKey = req.headers['idempotency-key'];
// Check if this key was already processed
const existing = await db.idempotencyKeys.findOne({ key: idempotencyKey });
if (existing) {
return res.status(existing.statusCode).json(existing.responseBody);
}
// Process the payment
const payment = await stripe.charges.create(req.body);
// Store the result keyed by idempotency key (TTL: 24 hours)
await db.idempotencyKeys.insert({
key: idempotencyKey,
statusCode: 201,
responseBody: payment,
expiresAt: new Date(Date.now() + 24 * 60 * 60 * 1000)
});
res.status(201).json(payment);
});Stripe, PayPal, and most payment APIs use this pattern. You should too for any operation that must not be duplicated.
OpenAPIWhat is openapi?A standard format for describing REST APIs - their endpoints, parameters, and response shapes. Tools can generate documentation and client libraries from it automatically. / Swagger
OpenAPI (formerly Swagger) is the standard for describing RESTWhat is rest?An architectural style for web APIs where URLs represent resources (nouns) and HTTP methods (GET, POST, PUT, DELETE) represent actions on those resources. APIs. You write a YAMLWhat is yaml?A human-readable text format used for configuration files, including Docker Compose and GitHub Actions workflows. or JSONWhat is json?A text format for exchanging data between systems. It uses key-value pairs and arrays, and every programming language can read and write it. spec, and tooling generates documentation, client SDKs, and server stubs.
openapi: 3.0.0
info:
title: Blog API
version: 1.0.0
paths:
/api/articles:
get:
summary: List articles
parameters:
- name: status
in: query
schema:
type: string
enum: [draft, published]
- name: limit
in: query
schema:
type: integer
default: 20
maximum: 100
responses:
'200':
description: List of articles
content:
application/json:
schema:
type: object
properties:
data:
type: array
items:
$ref: '#/components/schemas/Article'SchemaWhat is schema?A formal definition of the structure your data must follow - which fields exist, what types they have, and which are required.-first workflow: Write the OpenAPI spec before writing any code. The spec becomes the contract between frontend and backend teams. Both sides can work in parallel, frontend mocks the APIWhat is api?A set of rules that lets one program talk to another, usually over the internet, by sending requests and getting responses. from the spec, backend implements it.
What AI gets wrong when generating RESTWhat is rest?An architectural style for web APIs where URLs represent resources (nouns) and HTTP methods (GET, POST, PUT, DELETE) represent actions on those resources. APIs
Here is what AI typically generates when you ask for a REST APIWhat is api?A set of rules that lets one program talk to another, usually over the internet, by sending requests and getting responses., and why you should not trust the output without review:
Inconsistent naming: The AI might use createdAt on one endpointWhat is endpoint?A specific URL path on a server that handles a particular type of request, like GET /api/users. and created_at on another. Or userId in one response and user_id in the next. Pick camelCase or snake_case and enforce it everywhere.
Missing paginationWhat is pagination?Splitting a large set of results into smaller pages so the server and client only handle a manageable chunk at a time.: AI-generated list endpoints often return all items with no pagination. This works fine with 10 test records and breaks catastrophically in production with 100,000 rows.
No error format: Responses come back as { error: "something" } on one endpoint and { message: "something", code: 400 } on another. Define a standard error format and use it everywhere:
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Email format is invalid",
"details": [
{ "field": "email", "issue": "Must be a valid email address" }
]
}
}No versioning strategy: AI rarely adds API versioning. When you need to make a breaking changeWhat is breaking change?A modification to an API that causes existing code using it to stop working, such as renaming a field or changing a response format. six months later, you have no path forward. Always version your API from day one, /v1/articles, even if you never plan to change it.
Ignoring idempotency: AI-generated POST endpoints almost never include idempotency keyWhat is idempotency key?A unique client-generated string sent with a mutation request so the server can safely deduplicate retried requests. handling. For non-critical endpoints this is fine. For payments, orders, or anything involving money, it is a production incident waiting to happen.
RESTWhat is rest?An architectural style for web APIs where URLs represent resources (nouns) and HTTP methods (GET, POST, PUT, DELETE) represent actions on those resources. conventions table
| Convention | Do | Do not |
|---|---|---|
| Resource names | Plural nouns: /users, /articles | Verbs: /getUsers, /createArticle |
| URL casing | Lowercase kebab-case: /user-profiles | camelCase: /userProfiles |
| Response casing | Pick one: camelCase or snake_case | Mix both in the same API |
| HTTP methods | Use semantically: GET reads, POST creates | POST for everything |
| Status codes | Use specific codes: 201 Created, 204 No Content | 200 for everything |
| Error format | Consistent structure across all endpoints | Different shapes per endpoint |
| Versioning | URL prefix: /v1/ or header: Accept-Version | No versioning at all |
| Pagination | Always on list endpoints | Return unbounded arrays |
| Date format | ISO 8601: 2024-03-15T10:30:00Z | Unix timestamps or custom formats |
| Null fields | Include with null value or omit consistently | Sometimes include, sometimes omit |