What Is REST? The Architectural Style Explained
A clear introduction to REST — resources, URIs, HTTP verbs, statelessness, status codes, JSON conventions, and how REST compares to RPC and GraphQL.
What you'll learn
- ✓What REST actually means — beyond "an API with URLs"
- ✓How to model resources and URIs
- ✓How HTTP verbs map to operations
- ✓Why statelessness matters
- ✓Which status codes to use, and when
- ✓How REST compares to RPC and GraphQL
Prerequisites
- •A rough idea of HTTP — requests, responses, headers
- •Comfort with JSON
REST is one of those words people use loosely. Strictly speaking, it is an architectural style described by Roy Fielding in his 2000 PhD thesis. Loosely, it is the dominant way people design HTTP APIs today: resources at URLs, verbs for actions, JSON bodies, sensible status codes.
This post gives you a working definition you can apply when designing or consuming an API.
What REST actually is
REST stands for Representational State Transfer. The unpacked version:
- Representational — the client never touches the server’s internal data directly. It sees a representation (usually JSON) of the current state.
- State Transfer — every request carries enough information for the server to act. The server keeps no per-client memory between requests.
Fielding’s thesis defined six constraints. The ones you will care about day to day:
- Client-server. Separate the UI from the data store.
- Stateless. Each request is self-contained.
- Cacheable. Responses are explicit about whether and how long they can be cached.
- Uniform interface. Everything is shaped consistently — resources, methods, status codes.
- Layered system. Clients should not care if they are talking to the origin, a load balancer, or a CDN.
The sixth — code on demand — is rarely used. The first four are what define REST in practice.
Resources
A resource is anything worth naming in your system: a user, an order, a blog post, a collection of comments. In a REST API, every resource has a URL (formally a URI) that identifies it.
/users
/users/42
/users/42/orders
/orders/9001
Two patterns to internalise:
- Collections are plural nouns:
/users,/orders,/todos. - A single item is the collection plus an identifier:
/users/42.
Avoid verbs in URLs. The HTTP method is the verb — see the next section.
GET /users # list users
GET /users/42 # get user 42
POST /users # create a user
# Anti-pattern:
GET /getAllUsers
POST /createUser
HTTP verbs
Each method has a defined meaning:
| Method | Purpose | Idempotent | Safe |
|---|---|---|---|
| GET | Read a resource | Yes | Yes |
| POST | Create a new resource | No | No |
| PUT | Replace a resource | Yes | No |
| PATCH | Update part of a resource | No (in practice) | No |
| DELETE | Remove a resource | Yes | No |
Two terms worth knowing:
- Safe — does not change server state. Browsers will prefetch safe methods.
- Idempotent — calling N times has the same effect as calling once. Important for retries.
The mapping for a typical resource looks like this:
GET /todos # list
GET /todos/1 # read one
POST /todos # create
PUT /todos/1 # replace
PATCH /todos/1 # partial update
DELETE /todos/1 # delete
This is exactly the shape we built in Build Your First REST API with Express.
Statelessness
Every request must carry everything the server needs. The server does not remember the previous request. If the client is “logged in,” the credentials (a token, a cookie) accompany every request.
Why this matters:
- Horizontal scaling. Any server in a fleet can handle any request. No “sticky sessions” required.
- Reliability. A failed server can be replaced without losing per-client memory.
- Cacheability. A stateless request and its response are a self-contained pair — easy to cache.
Statelessness does not forbid databases. The resource has state. The conversation between client and server does not.
Representations
The same resource can have multiple representations — JSON, XML, an image. Clients ask for what they want via the Accept header:
Accept: application/json
Accept: application/xml
Accept: text/csv
In modern APIs, you almost always serve JSON. A typical response:
{
"id": 42,
"name": "Alice",
"email": "alice@example.com",
"createdAt": "2026-06-15T10:30:00Z"
}
Conventions worth following:
- camelCase for JSON keys in JavaScript ecosystems (snake_case in Python ones)
- ISO 8601 for timestamps (
2026-06-15T10:30:00Z) - Stable IDs — never change a resource’s ID once issued
- Include the ID in the resource body, not only in the URL
Status codes
HTTP defines a status code for every outcome. Use them; do not return 200 with {"error": "..."} in the body.
Successful:
- 200 OK — generic success with a body
- 201 Created — a POST created a resource; usually return the new resource
- 204 No Content — success with nothing to say (typical for DELETE)
Client errors (something is wrong with the request):
- 400 Bad Request — malformed or invalid input
- 401 Unauthorized — missing or bad credentials
- 403 Forbidden — authenticated but not allowed
- 404 Not Found — resource does not exist
- 409 Conflict — request collides with current state (e.g. duplicate email)
- 422 Unprocessable Entity — well-formed but semantically wrong (popular in some communities; 400 is fine too)
- 429 Too Many Requests — rate limit hit
Server errors (something is wrong on your side):
- 500 Internal Server Error — generic failure
- 502 Bad Gateway — an upstream service failed
- 503 Service Unavailable — temporary overload or maintenance
A request that looks fine but causes a server bug is 500, not 400.
Try it yourself. For each scenario, write down the right status code:
- A client POSTs
{}to/userswhenemailis required. - A client GETs
/users/9999and no such user exists. - A client DELETEs
/users/5and the deletion succeeds. - A client POSTs
/userswith an email that is already taken. - The database connection drops mid-request.
Answers: 400, 404, 204, 409, 500.
Query strings
Use the URL path for identifying resources. Use the query string for filters, sorting, paging, and search.
GET /todos?done=false&sort=createdAt&limit=20&page=2
GET /products?category=shoes&priceMax=100
GET /search?q=hello+world
Two paging conventions:
- Offset/limit — simple, fine for small datasets, breaks down on huge ones
- Cursor-based — pass an opaque token from the previous response; scales well
Versioning
APIs change. The two common strategies:
# In the URL
GET /v1/users
GET /v2/users
# In a header
GET /users
Accept: application/vnd.example.v2+json
URL versioning is more common because it is visible and easy to route. Header-based is purer (the URL is the resource, not the version) but harder to debug.
Bumping a major version is for breaking changes. Adding optional fields to a response is not breaking — old clients ignore them.
HATEOAS
You will occasionally hear the term HATEOAS — Hypermedia As The Engine Of Application State. The idea: responses include links to related resources, so clients discover the API by following links rather than knowing URLs in advance.
{
"id": 42,
"name": "Alice",
"links": {
"self": "/users/42",
"orders": "/users/42/orders"
}
}
In theory, this is what makes an API “truly RESTful.” In practice, very few real-world APIs do it. Most ship URL conventions in documentation and call themselves REST anyway. That trade-off is fine; just know the term if it comes up.
REST vs RPC
RPC (Remote Procedure Call) APIs are organised around actions, not resources:
POST /createUser
POST /sendEmail
POST /cancelOrder
JSON-RPC, gRPC, and SOAP are RPC styles. The trade-offs:
- RPC is great when your domain is action-heavy (“compute tax,” “cancel reservation”). gRPC in particular has excellent tooling for microservices.
- REST is great when your domain maps cleanly onto nouns and CRUD operations. It is also the default for public APIs because browsers, caches, and HTTP tooling already understand it.
Most teams pick REST and end up sneaking in the occasional RPC-style endpoint (POST /orders/123/cancel) where the resource model is awkward. That is fine. Pragmatism beats purity.
REST vs GraphQL
GraphQL is a query language that lets the client ask for exactly the fields it wants, in a single request:
{
user(id: 42) {
name
orders(last: 3) {
total
items { name price }
}
}
}
Strengths:
- One round trip can fetch nested data that REST needs several requests for
- The client picks the fields, reducing over-fetching
- Strongly typed schema with great editor tooling
Trade-offs:
- More server complexity — resolvers, schema, caching strategies
- HTTP caching is harder (everything is a POST to
/graphql) - Mental model is heavier for beginners
Use GraphQL when the client’s data needs are highly varied (a complex frontend hitting many entities). Use REST when the data model is roughly flat and shaped like resources. Many companies ship both — REST for partners and webhooks, GraphQL for their own frontend.
A “good enough” REST checklist
When you build or review a REST API, the questions worth asking:
- Are URLs resource-based with plural collections and IDs?
- Does each endpoint use the right HTTP method?
- Does each response carry the right status code?
- Is the API stateless — no per-client memory on the server?
- Are errors shaped consistently (
{"error": "..."}or similar)? - Do timestamps use ISO 8601 and IDs stay stable?
- Are paging, sorting, and filtering done with query strings?
Most “REST violations” you will see in the wild are not deep philosophical errors. They are tiny inconsistencies: a 200 OK with an error body, a POST that should have been a DELETE, an action verb in a URL. Polish those, and your API will feel professional.
Try it yourself. Sketch the URLs and methods for a blogging API with posts, comments on each post, and tags. Cover: list all posts; create a post; fetch one post with its comments; add a comment; delete a comment; list posts that have a given tag. Mark each line with the HTTP method.
Recap
You now know:
- REST is an architectural style, not a protocol
- Resources live at URLs; HTTP verbs are the actions
- The server is stateless — every request carries what it needs
- Status codes are the API’s way of speaking precisely
- JSON, ISO timestamps, and stable IDs are the conventions to follow
- RPC suits action-heavy domains; GraphQL suits clients with varied data needs
Next steps
You have the theory; you have an Express implementation. The natural progression from here is persistence (a database), validation (a schema library), authentication (tokens or sessions), and testing. Each builds on the resource-and-status mental model you now have.
For a hands-on starting point, revisit Build Your First REST API with Express and try replacing the in-memory array with a SQLite database.
Questions or feedback? Email codeloomdevv@gmail.com.
Related articles
- REST APIs HTTP Methods Explained: GET, POST, PUT, PATCH, DELETE
A practical walkthrough of HTTP methods — semantics, safety, idempotency, body conventions, and the differences between POST, PUT, and PATCH. Plus OPTIONS and HEAD.
- REST APIs REST API Design: Practical Best Practices
Field-tested REST API design — resource naming, versioning, pagination, filtering, sorting, idempotency, and consistent error envelopes. Conventions that age well in production.
- REST APIs HTTP Status Codes Every API Developer Should Know
A practical tour of HTTP status codes — 2xx success, 3xx redirects, 4xx client errors, 5xx server errors. Which to return when, and the common mistakes to avoid.
- GraphQL What Is GraphQL? A Query-First API Style
A clear introduction to GraphQL — a single endpoint, exact-fields queries, a typed schema, and how it compares to REST. Learn when GraphQL fits and when it doesn't.