Skip to main content

Designing and Consuming REST APIs

A REST API exposes resources over HTTP, usually returning JSON.

Resource-oriented URLs​

ActionRequestSuccess
ListGET /api/notes?limit=20&cursor=...200
ReadGET /api/notes/42200
CreatePOST /api/notes201 with Location header
ReplacePUT /api/notes/42200
UpdatePATCH /api/notes/42200
DeleteDELETE /api/notes/42204

Use nouns (/notes), plural names and nesting only when it expresses ownership (/users/7/notes).

Request and response shape​

{
"id": 42,
"text": "Learn REST",
"done": false,
"createdAt": "2026-10-14T09:30:00Z"
}

Errors should have a consistent body:

{
"error": {
"code": "VALIDATION_ERROR",
"message": "text must not be empty",
"fields": { "text": "required" }
}
}

Status codes you will use​

CodeUse
200 / 201 / 204OK / Created / No Content
400Invalid request
401Not authenticated
403Authenticated but not allowed
404Not found
409Conflict (duplicate, version mismatch)
422Valid syntax, failed validation
429Too many requests
500Server error

Validation example​

const { z } = require("zod");

const NoteInput = z.object({
text: z.string().min(1).max(500),
done: z.boolean().optional(),
});

app.post("/api/notes", (req, res) => {
const result = NoteInput.safeParse(req.body);
if (!result.success) {
return res.status(422).json({ error: { code: "VALIDATION_ERROR", fields: result.error.flatten().fieldErrors } });
}
// ... create the note
});

Consuming an API from the browser​

async function api(path, options = {}) {
const res = await fetch(`/api${path}`, {
headers: { "Content-Type": "application/json" },
...options,
});
if (!res.ok) {
const body = await res.json().catch(() => ({}));
throw new Error(body.error?.message ?? `HTTP ${res.status}`);
}
return res.status === 204 ? null : res.json();
}

const note = await api("/notes", { method: "POST", body: JSON.stringify({ text: "Hi" }) });

CORS in one paragraph​

Browsers block cross-origin requests unless the server sends Access-Control-Allow-Origin for your origin. Allow-list the origins you control; never use * with credentials.

See also API design and rate limiting.