Learn API Endpoint Patterns
57 patterns across 7 categories. Each one shows the convention, a side-by-side example, and why it matters.
Start here
New to API design? Follow these five categories in order.
REST API Design
Resource naming, HTTP methods, status codes, pagination, and versioning. You'll hit this when your endpoints grow inconsistent, clients break on API changes, or you can't decide between PUT and PATCH.
// Express route handlers
app.get("/getUsers", (req, res) => {
const users = db.findAllUsers();
res.json(users);
});
app.post("/createUser", (req, res) => {
const user = db.insertUser(req.body);
res.status(201).json(user);
});
app.delete("/deleteUser/:id", (req, res) => {
db.removeUser(req.params.id);
res.status(204).send();
});// Express route handlers
app.get("/getUsers", (req, res) => {
const users = db.findAllUsers();
res.json(users);
});
app.post("/createUser", (req, res) => {
const user = db.insertUser(req.body);
res.status(201).json(user);
});
app.delete("/deleteUser/:id", (req, res) => {
db.removeUser(req.params.id);
res.status(204).send();
});// Express route handlers
app.get("/users", (req, res) => {
const users = db.findAllUsers();
res.json(users);
});
app.post("/users", (req, res) => {
const user = db.insertUser(req.body);
res.status(201).json(user);
});
app.delete("/users/:id", (req, res) => {
db.removeUser(req.params.id);
res.status(204).send();
});// Express route handlers
app.get("/users", (req, res) => {
const users = db.findAllUsers();
res.json(users);
});
app.post("/users", (req, res) => {
const user = db.insertUser(req.body);
res.status(201).json(user);
});
app.delete("/users/:id", (req, res) => {
db.removeUser(req.params.id);
res.status(204).send();
});GraphQL Patterns
Query structure, mutations, fragments, N+1 problems, and when GraphQL beats REST. You'll hit this when nested resolvers slow your API to a crawl or clients over-fetch data they never use.
query GetOrderDetails($id: ID!) {
order(id: $id) {
id
status
customer {
id
name
address {
city
country
region {
name
timezone
}
}
}
}
}query GetOrderDetails($id: ID!) {
order(id: $id) {
id
status
customer {
id
name
address {
city
country
region {
name
timezone
}
}
}
}
}query GetOrderDetails($id: ID!) {
order(id: $id) {
id
status
customerId
}
}
query GetCustomer($id: ID!) {
customer(id: $id) {
id
name
city
country
}
}query GetOrderDetails($id: ID!) {
order(id: $id) {
id
status
customerId
}
}
query GetCustomer($id: ID!) {
customer(id: $id) {
id
name
city
country
}
}WebSockets & Real-time
When to use WebSockets vs SSE vs polling, connection lifecycle, reconnection, and message design. You'll hit this when you need live updates but long-polling is crushing your server or messages arrive out of order.
// WebSocket approach
const ws = new WebSocket("wss://api.example.com/prices");
ws.onopen = () => {
console.log("Connected");
};
ws.onmessage = (event) => {
const price = JSON.parse(event.data);
updateDashboard(price);
};// WebSocket approach
const ws = new WebSocket("wss://api.example.com/prices");
ws.onopen = () => {
console.log("Connected");
};
ws.onmessage = (event) => {
const price = JSON.parse(event.data);
updateDashboard(price);
};// EventSource approach
const source = new EventSource("/api/prices");
source.addEventListener("price-update", (event) => {
const price = JSON.parse(event.data);
updateDashboard(price);
});
source.onerror = () => {
console.log("Reconnecting automatically...");
};// EventSource approach
const source = new EventSource("/api/prices");
source.addEventListener("price-update", (event) => {
const price = JSON.parse(event.data);
updateDashboard(price);
});
source.onerror = () => {
console.log("Reconnecting automatically...");
};Authentication & Authorization
API keys vs OAuth vs JWT, token placement, scopes, rate limiting, and permission models. You'll hit this when tokens leak through query strings, JWTs grow too large, or rate limits punish legitimate users.
// Token in query string
fetch(
"/api/users?token=eyJhbGciOi..."
);// Token in query string
fetch(
"/api/users?token=eyJhbGciOi..."
);// Token in Authorization header
fetch("/api/users", {
headers: {
Authorization: "Bearer eyJhbGciOi...",
"Content-Type": "application/json",
},
});// Token in Authorization header
fetch("/api/users", {
headers: {
Authorization: "Bearer eyJhbGciOi...",
"Content-Type": "application/json",
},
});Error Handling
Error response structure, retry strategies, idempotency, timeouts, and graceful degradation. You'll hit this when clients can't tell a validation error from a server crash, or retries cause duplicate orders.
// Simple error response
app.use((err, req, res, next) => {
res.status(err.status || 500).json({
error: err.message,
});
});// Simple error response
app.use((err, req, res, next) => {
res.status(err.status || 500).json({
error: err.message,
});
});// RFC 7807 Problem Details
app.use((err, req, res, next) => {
res.status(err.status || 500).json({
type: "https://api.example.com/errors/validation",
title: "Validation Error",
status: err.status,
detail: err.message,
instance: req.originalUrl,
});
});// RFC 7807 Problem Details
app.use((err, req, res, next) => {
res.status(err.status || 500).json({
type: "https://api.example.com/errors/validation",
title: "Validation Error",
status: err.status,
detail: err.message,
instance: req.originalUrl,
});
});API Consumption
Caching strategies, request deduplication, batching, optimistic updates, and loading states. You'll hit this when the same endpoint is called five times on one page load or stale data lingers after a mutation.
function UserProfile({ id }: { id: string }) {
const [user, setUser] = useState<User | null>(null);
useEffect(() => {
fetch(`/api/users/${id}`)
.then((res) => res.json())
.then(setUser);
}, [id]);
if (!user) return <Skeleton />;
return <Profile user={user} />;
}function UserProfile({ id }: { id: string }) {
const [user, setUser] = useState<User | null>(null);
useEffect(() => {
fetch(`/api/users/${id}`)
.then((res) => res.json())
.then(setUser);
}, [id]);
if (!user) return <Skeleton />;
return <Profile user={user} />;
}function UserProfile({ id }: { id: string }) {
const { data: user } = useSWR(
`/api/users/${id}`,
fetcher,
{ revalidateOnFocus: true }
);
if (!user) return <Skeleton />;
return <Profile user={user} />;
}function UserProfile({ id }: { id: string }) {
const { data: user } = useSWR(
`/api/users/${id}`,
fetcher,
{ revalidateOnFocus: true }
);
if (!user) return <Skeleton />;
return <Profile user={user} />;
}Documentation & Contracts
OpenAPI vs hand-written docs, schema validation, versioning communication, and SDK generation. You'll hit this when frontend and backend disagree on the shape of a response, or API docs are perpetually outdated.
# Hand-written API docs (README.md)
# POST /users
# Body: { name, email, age }
# Returns: User object
#
# Note: age is optional
# Updated: March 2024# Hand-written API docs (README.md)
# POST /users
# Body: { name, email, age }
# Returns: User object
#
# Note: age is optional
# Updated: March 2024# openapi.yaml
paths:
/users:
post:
requestBody:
content:
application/json:
schema:
$ref: "#/components/schemas/CreateUser"
responses:
"201":
description: User created
content:
application/json:
schema:
$ref: "#/components/schemas/User"# openapi.yaml
paths:
/users:
post:
requestBody:
content:
application/json:
schema:
$ref: "#/components/schemas/CreateUser"
responses:
"201":
description: User created
content:
application/json:
schema:
$ref: "#/components/schemas/User"