REST (Representational State Transfer) is an architectural style for distributed systems, introduced by Roy Fielding in 2000. A REST API models a system as a set of resources identified by URLs, manipulated through a uniform interface (HTTP methods), where each request carries all the context the server needs to process it.
Core ideas
- Resource, any concept worth a URL: a user, an order, a collection. Identified by a URI like
/orders/42. - Representation, the current state of a resource serialized for transfer, usually JSON. Clients act on representations, not the resource directly.
- Uniform interface, the same small set of verbs and status codes works across every resource, so clients and servers evolve independently.
Constraints
Fielding defined REST through six constraints. An API is “RESTful” only when it honors them:
- Client-server, separate the user interface from data storage.
- Stateless, the server keeps no client session between requests; each request is self-contained.
- Cacheable, responses declare whether they can be cached.
- Uniform interface, consistent resource identification and manipulation.
- Layered system, proxies, gateways, and load balancers can sit between client and server transparently.
- Code on demand (optional), servers may return executable code.
HTTP methods
Verbs map to operations, with safety (no side effects) and idempotency (repeatable without extra effect) properties that clients rely on for retries:
| Method | Use | Safe | Idempotent |
|---|---|---|---|
GET |
Read a resource | Yes | Yes |
POST |
Create / non-idempotent action | No | No |
PUT |
Replace a resource | No | Yes |
PATCH |
Partial update | No | No |
DELETE |
Remove a resource | No | Yes |
Reading a single resource is a GET against its URL. The JSON representation comes back with status, fields, and HATEOAS _links:
GET /orders/42 HTTP/1.1
Host: api.example.com
Accept: application/json
HTTP/1.1 200 OK
Content-Type: application/json
{
"id": 42,
"status": "shipped",
"total": 79.90,
"_links": { "self": "/orders/42", "customer": "/customers/7" }
}const res = await fetch("https://api.example.com/orders/42", {
headers: { Accept: "application/json" },
});
const order = await res.json();
// { id: 42, status: "shipped", total: 79.90,
// _links: { self: "/orders/42", customer: "/customers/7" } }interface Order {
id: number;
status: string;
total: number;
_links: Record<string, string>;
}
const res = await fetch("https://api.example.com/orders/42", {
headers: { Accept: "application/json" },
});
const order: Order = await res.json();using var client = new HttpClient();
client.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json"));
var order = await client.GetFromJsonAsync<Order>(
"https://api.example.com/orders/42");import httpx
res = httpx.get(
"https://api.example.com/orders/42",
headers={"Accept": "application/json"},
)
res.raise_for_status()
order = res.json()Status codes
REST leans on HTTP status codes to signal outcomes: 2xx success, 3xx redirection, 4xx client error (400 bad request, 404 not found, 409 conflict), 5xx server error. Meaningful codes let clients react without parsing a custom error envelope.
Maturity and HATEOAS
The Richardson Maturity Model grades how fully an API adopts REST: from a single endpoint (level 0), to resources (level 1), to HTTP verbs (level 2), to HATEOAS (level 3), where responses embed links (like _links above) that tell clients which transitions are available next. Most production APIs stop at level 2.
When to use REST
REST is the default for public, resource-oriented HTTP APIs: broad tooling, cacheability, and a low learning curve. Reach for alternatives when its grain does not fit:
- GraphQL, clients need to shape responses and avoid over- or under-fetching across many related entities.
- gRPC, low-latency, strongly typed service-to-service calls.
- Event-driven architecture, asynchronous, fire-and-forget flows rather than request/response. Webhooks push REST-style callbacks back to clients; see webhook reliability patterns.
