Skip to main content

REST APIs

A REST API exposes resources through a consistent HTTP interface so clients and servers can communicate without depending on each other's internal implementation.

Client describes the desired operation in an HTTP request
↓
Server validates and processes the request
↓
Server returns an HTTP response and resource representation

One-line idea: REST models application data as resources and uses standard HTTP semantics to create, read, update and delete them.

Core REST Concepts​

REST Is an Architectural Style​

REST stands for Representational State Transfer. It is not a programming language, framework or transport protocol.

An API can follow REST principles while being implemented with Node.js, Java, Python, Go or another backend technology.

HTTP provides the communication mechanism. REST provides design constraints for using that mechanism consistently.

  • Platform independent: browsers, mobile apps, servers and devices can use HTTP.
  • Separation of concerns: clients and servers evolve independently behind a stable contract.
  • Scalable: stateless requests can be distributed across server instances.
  • Familiar: methods, headers and status codes reuse standard HTTP behavior.
  • Cacheable: suitable responses can be cached by browsers, proxies and CDNs.
  • Flexible representations: JSON is common, but REST does not require it.
  • Integration friendly: public and internal services can expose the same resource model.

REST Constraints​

A RESTful system follows these main constraints:

ConstraintMeaning
Client-serverPresentation and data responsibilities are separated
StatelessEvery request contains the context required to process it
CacheableResponses state whether and how they may be reused
Uniform interfaceResources use consistent identifiers, representations and HTTP semantics
Layered systemClients need not know whether gateways, proxies or services handle requests
Code on demandOptional ability to send executable code to a client

The first five constraints define the usual REST API model. Code on demand is optional and uncommon for JSON APIs.

Stateless Does Not Mean No State​

The server can store users, orders, sessions and other application data.

Stateless means a server does not rely on hidden conversational context from a previous request.

Request 1: contains authentication and required input
Request 2: also contains authentication and required input

Any healthy server instance should be able to handle either request.

Architecture and Request Flow​

Application Tiers​

One-Tier Architecture​

Presentation, business logic and data exist in one application.

UI + business logic + data

It is simple for small local tools but tightly coupled.

Two-Tier Architecture​

A client communicates directly with a server or database-backed application.

Client <-> Server and data

Three-Tier Architecture​

Presentation, application logic and data storage are separated.

Client <-> API / application server <-> Database

Most production web APIs follow three-tier or more layered architectures. A gateway, cache, load balancer or multiple services may sit between the client and final data store.

Request-Response Cycle​

Client
|
| HTTP request
v
Server -> authentication -> validation -> business logic -> data access
|
| HTTP response
v
Client

The client always initiates a normal HTTP request. The server processes it and returns a response containing status, metadata and optionally a body.

HTTP Request Structure​

An HTTP request contains:

  1. a request line;
  2. headers;
  3. an optional body.
POST /v1/todos HTTP/1.1
Host: api.example.com
Authorization: Bearer <token>
Content-Type: application/json
Accept: application/json

{
"task": "Study REST APIs"
}

Request Line​

METHOD path HTTP-version

It identifies the operation and target resource.

Request Headers​

Headers provide metadata such as authentication, accepted formats, language, caching conditions and content type.

Request Body​

The body contains input data for methods such as POST, PUT and PATCH. GET and HEAD requests normally do not use a body because intermediaries do not handle such bodies consistently.

HTTP Response Structure​

An HTTP response contains:

  1. a status line;
  2. headers;
  3. an optional body.
HTTP/1.1 201 Created
Content-Type: application/json
Location: /v1/todos/42
ETag: "todo-42-v1"

{
"id": "42",
"task": "Study REST APIs",
"completed": false
}

The status code communicates the outcome. Headers describe the response, while the body contains the returned representation or error details.

URL Anatomy​

Consider this URL:

https://api.example.com/v1/questions?tag=networking&order=newest#top
PartExamplePurpose
SchemehttpsCommunication protocol
Hostapi.example.comDestination domain
Path/v1/questionsResource location
Query stringtag=networking&order=newestFiltering or representation options
FragmenttopClient-side location inside a representation

The fragment after # is not sent in the HTTP request. It is interpreted by the client.

Path Parameters​

Path parameters identify a specific resource or nested scope:

GET /v1/users/123
GET /v1/users/123/orders
GET /v1/orders/ord_456/items

Query Parameters​

Query parameters modify a collection view:

GET /v1/products?category=books&sort=price&limit=20

Common uses include filtering, sorting, pagination, search and field selection.

Resource-Oriented Design​

Use Nouns for Resources​

Prefer resource names over action names:

Good
GET /v1/todos
POST /v1/todos
GET /v1/todos/42
PATCH /v1/todos/42
DELETE /v1/todos/42

Avoid
GET /v1/getTodos
POST /v1/createTodo
POST /v1/deleteTodo

The HTTP method already describes the operation.

Use Consistent Naming​

  • use plural collection names;
  • keep path casing consistent, usually lowercase;
  • use hyphens if words must be separated;
  • avoid leaking database table or implementation names;
  • keep nesting shallow;
  • use stable opaque identifiers.

Model Relationships Carefully​

Nested paths are useful when the parent provides essential context:

GET /v1/users/123/orders
POST /v1/orders/456/items

Deep paths become difficult to maintain:

/users/123/orders/456/items/789/reviews/55/comments

When a child has its own stable identity, provide a direct endpoint as well.

HTTP Methods​

GET​

Retrieves a resource or collection.

GET /v1/todos
GET /v1/todos/42

GET is safe because it should not request a state change. It is also idempotent because repeating it should have the same intended effect.

POST​

Creates a subordinate resource or triggers a non-idempotent operation.

POST /v1/todos
POST /v1/orders/123/refunds

POST is not idempotent by default. Retrying a create request can create duplicates unless the API supports an idempotency key.

PUT​

Creates or completely replaces the resource at a known URI.

PUT /v1/todos/42

A client usually sends the full replaceable representation. PUT is idempotent: repeating the same request should leave the resource in the same state.

PATCH​

Partially updates a resource.

PATCH /v1/todos/42

{
"completed": true
}

PATCH is not automatically idempotent. Setting a value can be idempotent, while an instruction such as incrementing a counter is not.

DELETE​

Removes or marks a resource as deleted.

DELETE /v1/todos/42

DELETE is idempotent in intended effect. Repeating it leaves the resource absent, although later responses may differ between 204 and 404 depending on the contract.

Returns the same headers as GET without a response body.

It is useful for checking metadata, existence, size or cache validators.

OPTIONS​

Describes communication options for a resource. Browsers also use OPTIONS for CORS preflight requests.

OPTIONS /v1/todos
Allow: GET, POST, HEAD, OPTIONS

CONNECT and TRACE​

  • CONNECT creates a tunnel, commonly through an HTTP proxy.
  • TRACE reflects request information for diagnostics.

Application APIs rarely implement them. TRACE is commonly disabled because reflected credentials or headers can create security risk.

Safety and Idempotency​

MethodSafeIdempotent by definitionCommon purpose
GETYesYesRead resource
HEADYesYesRead headers
OPTIONSYesYesDiscover communication
POSTNoNoCreate or execute action
PUTNoYesReplace resource
PATCHNoNot guaranteedPartially update resource
DELETENoYesRemove resource

Idempotent does not mean the response must be byte-for-byte identical. Logging, timestamps or a later 404 can differ while the requested resource state remains the same.

CRUD and HTTP​

CRUD describes common data operations:

CRUDTypical HTTP methodExample
CreatePOSTPOST /todos
ReadGETGET /todos/42
UpdatePUT or PATCHPATCH /todos/42
DeleteDELETEDELETE /todos/42

REST is broader than CRUD. APIs can also model operations as subordinate resources:

POST /v1/orders/123/cancellations
POST /v1/reports
GET /v1/reports/rpt_9

This is often clearer than inventing action-heavy paths.

HTTP Status Codes​

1xx - Informational​

Intermediate protocol responses.

CodeMeaningTypical use
100ContinueClient may continue sending a request body
101Switching ProtocolsProtocol upgrade, such as WebSocket

Application code rarely handles most informational responses directly.

2xx - Success​

CodeMeaningTypical use
200OKSuccessful request with a response body
201CreatedResource created; often includes Location
202AcceptedWork accepted but not completed
204No ContentSuccessful request with no response body

3xx - Redirection and Caching​

CodeMeaningTypical use
301Moved PermanentlyPermanent redirect, method may change in some clients
302FoundTemporary redirect with historical method behavior
303See OtherRead another resource with GET
304Not ModifiedCached representation remains valid
307Temporary RedirectTemporary redirect preserving method and body
308Permanent RedirectPermanent redirect preserving method and body

4xx - Client Errors​

CodeMeaningTypical use
400Bad RequestMalformed request or basic validation failure
401UnauthorizedAuthentication is missing or invalid
403ForbiddenIdentity is known but lacks permission
404Not FoundResource does not exist or is intentionally hidden
405Method Not AllowedResource exists but method is unsupported
409ConflictRequest conflicts with current resource state
412Precondition FailedConditional request does not match current state
413Content Too LargeRequest body exceeds the allowed size
415Unsupported Media TypeBody format is not supported
422Unprocessable ContentSyntax is valid but business validation fails
429Too Many RequestsRate limit exceeded

401 means the client must authenticate correctly. 403 means authentication may be valid, but the action is not allowed.

5xx - Server Errors​

CodeMeaningTypical use
500Internal Server ErrorUnexpected server failure
501Not ImplementedServer does not support the required feature
502Bad GatewayInvalid response from an upstream service
503Service UnavailableTemporary overload or maintenance
504Gateway TimeoutUpstream service did not respond in time

Do not return 200 OK with an error hidden in the response body. HTTP status and body should agree.

Important HTTP Headers​

Request Headers​

HeaderPurpose
HostTarget host
OriginOrigin making a cross-origin request
RefererPage that initiated navigation or request
User-AgentClient software information
AcceptResponse media types the client can process
Accept-LanguagePreferred response languages
Accept-EncodingSupported compression encodings
AuthorizationAuthentication credentials
CookieCookies associated with the target
If-None-MatchCache validation using an ETag
If-MatchConditional mutation using an ETag
Cache-ControlRequest caching directives
Idempotency-KeyApplication-level protection for retried requests

Response Headers​

HeaderPurpose
Content-TypeResponse body media type
Content-LengthResponse size in bytes when known
Content-EncodingCompression applied to the body
LocationURI of a created or redirected resource
ETagIdentifier for one representation version
Last-ModifiedResource modification time
Cache-ControlCaching permission and freshness rules
Set-CookieCookie to store in the client
Access-Control-Allow-OriginOrigins allowed by CORS
Retry-AfterSuggested retry time after throttling or outage
VaryRequest headers that affect cached output

Avoid exposing detailed server software versions unless operationally necessary.

Representations and Content Negotiation​

A resource is the conceptual entity. A representation is the format sent across the network.

Resource: user 123

Possible representations:
application/json
text/html
application/xml

Clients describe acceptable response types with Accept. Requests containing a body describe its type with Content-Type.

Accept: application/json
Content-Type: application/json

Return 415 Unsupported Media Type when the request body format is unsupported and 406 Not Acceptable when no acceptable response representation is available.

JSON is common because it is compact, readable and supported across platforms, but it is not a REST requirement.

Request Validation and Errors​

Validate requests in layers:

Parse body
↓
Validate content type and schema
↓
Authenticate identity
↓
Authorize resource action
↓
Validate business rules
↓
Execute operation

Use one predictable error shape:

{
"error": {
"code": "VALIDATION_FAILED",
"message": "The request contains invalid fields.",
"details": [
{
"field": "task",
"reason": "must not be empty"
}
],
"requestId": "req_123"
}
}

Stable machine-readable codes are more useful than asking clients to parse human messages.

Do not expose stack traces, database errors, tokens or internal service details.

Collections, Filtering and Pagination​

Filtering​

GET /v1/todos?completed=false&ownerId=user_7

Whitelist supported fields and operators. Do not translate arbitrary query parameters directly into database expressions.

Sorting​

GET /v1/todos?sort=-createdAt,title

This example sorts by newest creation time, then title.

Offset Pagination​

GET /v1/todos?limit=20&offset=40

It is simple and supports jumping to a page, but large offsets can be slow and concurrent inserts may create duplicates or gaps.

Cursor Pagination​

GET /v1/todos?limit=20&cursor=opaque-token

The cursor contains stable ordering information and works better for large or changing collections.

{
"items": [],
"nextCursor": "opaque-token",
"hasMore": true
}

Keep cursors opaque so clients do not depend on internal storage details.

Caching and Conditional Requests​

Freshness Caching​

Cache-Control: public, max-age=60

The response can be reused for 60 seconds without contacting the origin.

Private user responses should normally use private or no-store according to their sensitivity.

ETag Validation​

Initial response:

HTTP/1.1 200 OK
ETag: "todos-v9"

Later request:

GET /v1/todos
If-None-Match: "todos-v9"

If nothing changed:

HTTP/1.1 304 Not Modified

The client reuses its cached body, saving bandwidth.

Cache Keys​

If output changes based on language, encoding, origin or another request header, use Vary correctly.

Never let a shared cache serve one user's private response to another user.

Concurrency Control​

Two clients can read the same resource and then overwrite each other.

Client A reads version 5
Client B reads version 5
Client A updates -> version 6
Client B updates stale version 5 -> risk of lost update

Use a conditional request:

PATCH /v1/todos/42
If-Match: "todo-42-v5"

If the current resource is no longer version 5, return:

HTTP/1.1 412 Precondition Failed

The client can reload, merge and retry instead of silently overwriting newer data.

Idempotency and Retries​

Networks fail ambiguously. A client may time out after the server completed an operation.

For operations such as payment or order creation:

POST /v1/orders
Idempotency-Key: 2cb1...

The server stores:

  • key;
  • authenticated operation scope;
  • request fingerprint;
  • processing state;
  • final status and response;
  • expiry time.

Repeating the same key and request returns the original outcome. Reusing the key with different input is rejected.

Idempotency makes retries safer but does not replace database constraints or transactions.

Authentication and Authorization​

Authentication​

Proves who the caller is.

Common mechanisms include:

  • secure session cookies;
  • bearer access tokens;
  • API keys for service integrations;
  • signed requests for sensitive machine communication.

Authorization​

Checks whether the authenticated caller may access this resource and action.

valid token does not mean access to every user's data

Always scope database access by the authorized owner, tenant or role. Hiding a button in the UI is not authorization.

  • use HTTPS;
  • keep credentials short-lived where practical;
  • protect cookies with HttpOnly, Secure and appropriate SameSite settings;
  • do not place secrets in URLs;
  • rotate and revoke credentials;
  • avoid logging authorization headers.

CORS​

Cross-Origin Resource Sharing controls whether browser JavaScript from one origin can read a response from another origin.

For some requests, the browser sends a preflight:

OPTIONS /v1/todos
Origin: https://app.example.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: content-type, authorization

The server may respond:

Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: GET, POST
Access-Control-Allow-Headers: Content-Type, Authorization

CORS is a browser policy, not authentication. Non-browser clients are not stopped by it.

Do not combine credentialed requests with a wildcard allowed origin.

Rate Limiting​

Rate limits protect availability and fair usage.

Possible keys include:

  • IP address;
  • user ID;
  • API key;
  • tenant;
  • endpoint or operation cost.

When a limit is exceeded:

HTTP/1.1 429 Too Many Requests
Retry-After: 30

Use different limits for inexpensive reads, expensive searches, authentication attempts and large uploads.

The client should use bounded retries with exponential backoff and jitter rather than retrying immediately in a loop.

Asynchronous Operations​

Long-running work should not hold an HTTP connection indefinitely.

POST /v1/reports
HTTP/1.1 202 Accepted
Location: /v1/report-jobs/job_42

The client reads job state:

GET /v1/report-jobs/job_42
{
"id": "job_42",
"status": "PROCESSING",
"progress": 60
}

The final result can be returned through the job resource, a signed download or a webhook for trusted integrations.

API Versioning and Evolution​

APIs are contracts. Prefer backward-compatible evolution:

  • add optional response fields;
  • add optional request fields with safe defaults;
  • add new endpoints;
  • preserve existing field meaning;
  • tolerate clients ignoring unknown response fields.

Breaking changes require a versioning and migration strategy.

Common approaches include:

URL version:    /v1/todos
Header version: Accept: application/vnd.example.v2+json

URL versions are visible and simple. Header versions keep resource URLs cleaner but are harder to inspect manually.

Do not create a new version for every additive field.

Small Express Example​

const express = require("express");

const app = express();
app.use(express.json({ limit: "32kb" }));

const todos = new Map();

app.get("/v1/todos/:id", (req, res) => {
const todo = todos.get(req.params.id);

if (!todo) {
return res.status(404).json({
error: { code: "TODO_NOT_FOUND", message: "Todo not found." },
});
}

return res.status(200).json(todo);
});

app.post("/v1/todos", (req, res) => {
const task = req.body.task?.trim();

if (!task) {
return res.status(422).json({
error: {
code: "VALIDATION_FAILED",
message: "task is required.",
},
});
}

const todo = {
id: crypto.randomUUID(),
task,
completed: false,
};

todos.set(todo.id, todo);

return res
.location(`/v1/todos/${todo.id}`)
.status(201)
.json(todo);
});

app.listen(3000);

The in-memory map is suitable only for learning. Production persistence needs durable storage, validation, authentication, authorization, concurrency handling and operational safeguards.

Complete REST Request Flow​

Client builds resource URL and request
↓
DNS and transport establish connectivity
↓
Gateway or server receives HTTP request
↓
Parse headers, path, query and body
↓
Authenticate and authorize caller
↓
Validate schema and business rules
↓
Execute resource operation
↓
Commit durable state if needed
↓
Build status, headers and representation
↓
Client interprets response consistently

Common Mistakes​

MistakeBetter approach
Calling every HTTP JSON endpoint RESTFollow resource and uniform-interface constraints
Using verbs in every pathUse nouns and let HTTP methods express the operation
Treating PUT and PATCH as identicalPUT replaces; PATCH applies a partial modification
Returning 200 for every outcomeUse status codes that match the result
Returning a body with 204 No ContentEnd the response without a body
Sending stack traces to clientsReturn safe stable error codes and log details internally
Trusting request IDs or ownership from the clientAuthorize every resource on the server
Retrying POST blindlyUse idempotency keys for operations that must not duplicate
Ignoring concurrent updatesUse resource versions, ETags or transactional checks
Using GET for state-changing operationsKeep safe methods free of requested side effects
Putting secrets in query stringsUse secure headers or bodies because URLs are widely logged
Allowing every CORS originPermit only required origins and treat CORS separately from auth
Returning an unlimited collectionAdd bounded pagination
Caching private data publiclySet correct cache scope and include all representation dimensions
Exposing database structure directlyDesign a stable domain contract
Enabling TRACE without a reasonDisable unnecessary methods and minimize reflected sensitive data

Summary​

REST APIs expose resources through a uniform, stateless and cache-aware HTTP interface.

Identify resources and stable URLs
↓
Choose correct HTTP method semantics
↓
Validate, authenticate and authorize
↓
Return meaningful status, headers and representation
↓
Support caching, pagination, concurrency and safe retries
↓
Evolve the contract without breaking clients

A good REST API is predictable: clients can understand what an endpoint means from its resource URL, HTTP method, status code, headers and documented representation without knowing how the server is implemented.