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.
Why REST APIs Are Popular
- 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:
| Constraint | Meaning |
|---|---|
| Client-server | Presentation and data responsibilities are separated |
| Stateless | Every request contains the context required to process it |
| Cacheable | Responses state whether and how they may be reused |
| Uniform interface | Resources use consistent identifiers, representations and HTTP semantics |
| Layered system | Clients need not know whether gateways, proxies or services handle requests |
| Code on demand | Optional 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:
- a request line;
- headers;
- 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:
- a status line;
- headers;
- 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
| Part | Example | Purpose |
|---|---|---|
| Scheme | https | Communication protocol |
| Host | api.example.com | Destination domain |
| Path | /v1/questions | Resource location |
| Query string | tag=networking&order=newest | Filtering or representation options |
| Fragment | top | Client-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.
HEAD
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
| Method | Safe | Idempotent by definition | Common purpose |
|---|---|---|---|
| GET | Yes | Yes | Read resource |
| HEAD | Yes | Yes | Read headers |
| OPTIONS | Yes | Yes | Discover communication |
| POST | No | No | Create or execute action |
| PUT | No | Yes | Replace resource |
| PATCH | No | Not guaranteed | Partially update resource |
| DELETE | No | Yes | Remove 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:
| CRUD | Typical HTTP method | Example |
|---|---|---|
| Create | POST | POST /todos |
| Read | GET | GET /todos/42 |
| Update | PUT or PATCH | PATCH /todos/42 |
| Delete | DELETE | DELETE /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.
| Code | Meaning | Typical use |
|---|---|---|
| 100 | Continue | Client may continue sending a request body |
| 101 | Switching Protocols | Protocol upgrade, such as WebSocket |
Application code rarely handles most informational responses directly.
2xx - Success
| Code | Meaning | Typical use |
|---|---|---|
| 200 | OK | Successful request with a response body |
| 201 | Created | Resource created; often includes Location |
| 202 | Accepted | Work accepted but not completed |
| 204 | No Content | Successful request with no response body |
3xx - Redirection and Caching
| Code | Meaning | Typical use |
|---|---|---|
| 301 | Moved Permanently | Permanent redirect, method may change in some clients |
| 302 | Found | Temporary redirect with historical method behavior |
| 303 | See Other | Read another resource with GET |
| 304 | Not Modified | Cached representation remains valid |
| 307 | Temporary Redirect | Temporary redirect preserving method and body |
| 308 | Permanent Redirect | Permanent redirect preserving method and body |
4xx - Client Errors
| Code | Meaning | Typical use |
|---|---|---|
| 400 | Bad Request | Malformed request or basic validation failure |
| 401 | Unauthorized | Authentication is missing or invalid |
| 403 | Forbidden | Identity is known but lacks permission |
| 404 | Not Found | Resource does not exist or is intentionally hidden |
| 405 | Method Not Allowed | Resource exists but method is unsupported |
| 409 | Conflict | Request conflicts with current resource state |
| 412 | Precondition Failed | Conditional request does not match current state |
| 413 | Content Too Large | Request body exceeds the allowed size |
| 415 | Unsupported Media Type | Body format is not supported |
| 422 | Unprocessable Content | Syntax is valid but business validation fails |
| 429 | Too Many Requests | Rate limit exceeded |
401 means the client must authenticate correctly. 403 means authentication may be valid, but the action is not allowed.
5xx - Server Errors
| Code | Meaning | Typical use |
|---|---|---|
| 500 | Internal Server Error | Unexpected server failure |
| 501 | Not Implemented | Server does not support the required feature |
| 502 | Bad Gateway | Invalid response from an upstream service |
| 503 | Service Unavailable | Temporary overload or maintenance |
| 504 | Gateway Timeout | Upstream 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
| Header | Purpose |
|---|---|
Host | Target host |
Origin | Origin making a cross-origin request |
Referer | Page that initiated navigation or request |
User-Agent | Client software information |
Accept | Response media types the client can process |
Accept-Language | Preferred response languages |
Accept-Encoding | Supported compression encodings |
Authorization | Authentication credentials |
Cookie | Cookies associated with the target |
If-None-Match | Cache validation using an ETag |
If-Match | Conditional mutation using an ETag |
Cache-Control | Request caching directives |
Idempotency-Key | Application-level protection for retried requests |
Response Headers
| Header | Purpose |
|---|---|
Content-Type | Response body media type |
Content-Length | Response size in bytes when known |
Content-Encoding | Compression applied to the body |
Location | URI of a created or redirected resource |
ETag | Identifier for one representation version |
Last-Modified | Resource modification time |
Cache-Control | Caching permission and freshness rules |
Set-Cookie | Cookie to store in the client |
Access-Control-Allow-Origin | Origins allowed by CORS |
Retry-After | Suggested retry time after throttling or outage |
Vary | Request 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.
Cookie and Token Safety
- use HTTPS;
- keep credentials short-lived where practical;
- protect cookies with
HttpOnly,Secureand appropriateSameSitesettings; - 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
| Mistake | Better approach |
|---|---|
| Calling every HTTP JSON endpoint REST | Follow resource and uniform-interface constraints |
| Using verbs in every path | Use nouns and let HTTP methods express the operation |
| Treating PUT and PATCH as identical | PUT replaces; PATCH applies a partial modification |
Returning 200 for every outcome | Use status codes that match the result |
Returning a body with 204 No Content | End the response without a body |
| Sending stack traces to clients | Return safe stable error codes and log details internally |
| Trusting request IDs or ownership from the client | Authorize every resource on the server |
| Retrying POST blindly | Use idempotency keys for operations that must not duplicate |
| Ignoring concurrent updates | Use resource versions, ETags or transactional checks |
| Using GET for state-changing operations | Keep safe methods free of requested side effects |
| Putting secrets in query strings | Use secure headers or bodies because URLs are widely logged |
| Allowing every CORS origin | Permit only required origins and treat CORS separately from auth |
| Returning an unlimited collection | Add bounded pagination |
| Caching private data publicly | Set correct cache scope and include all representation dimensions |
| Exposing database structure directly | Design a stable domain contract |
| Enabling TRACE without a reason | Disable 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.