You've seen the term everywhere — REST API. But what exactly is it, why does every web app use one, and how do you build or consume one correctly?
This guide covers REST from first principles to production patterns.
What Is a REST API?
REST (Representational State Transfer) is an architectural style for building web services. A REST API (or RESTful API) is an interface that lets two systems communicate over HTTP using standard rules.
Key idea: resources (users, products, orders) are identified by URLs, and you interact with them using standard HTTP methods.
GET /api/users/42 → read user 42
PUT /api/users/42 → replace user 42
PATCH /api/users/42 → partially update user 42
DELETE /api/users/42 → delete user 42
POST /api/users → create a new user
REST was defined by Roy Fielding in his 2000 PhD dissertation. It's not a protocol or standard — it's a set of constraints that, when followed, produce scalable, predictable APIs.
The 6 REST Constraints
| Constraint | What It Means |
|---|---|
| Client–Server | Frontend and backend are separate; they communicate only via API |
| Stateless | Each request contains all the information needed; server stores no session state |
| Cacheable | Responses must declare whether they can be cached (via HTTP headers) |
| Uniform Interface | Resources are identified by URLs; interactions happen through representations |
| Layered System | Client doesn't know if it's talking to the real server or a proxy/cache |
| Code on Demand (optional) | Server can send executable code (e.g., JavaScript) to the client |
Stateless is the most important constraint. It means every HTTP request must carry all context — authentication token, parameters, body — because the server doesn't remember previous requests.
HTTP Methods (Verbs)
| Method | Purpose | Body? | Safe? | Idempotent? |
|---|---|---|---|---|
GET |
Read a resource | No | Yes | Yes |
POST |
Create a resource | Yes | No | No |
PUT |
Replace a resource entirely | Yes | No | Yes |
PATCH |
Partially update a resource | Yes | No | Usually |
DELETE |
Delete a resource | No | No | Yes |
HEAD |
Like GET but no body (check if resource exists) | No | Yes | Yes |
OPTIONS |
Discover allowed methods (used in CORS preflight) | No | Yes | Yes |
Safe = doesn't change server state. Idempotent = calling it multiple times has the same effect as calling it once.
URL Design (Resource Naming)
Good REST URLs are nouns, not verbs. The HTTP method is the verb.
# Good — nouns, hierarchical
GET /api/users
GET /api/users/42
GET /api/users/42/orders
POST /api/users
DELETE /api/users/42
# Bad — verbs in URL
GET /api/getUser?id=42
POST /api/deleteUser/42
GET /api/fetchUserOrders/42
Rules of thumb:
- Use plural nouns:
/users,/products,/orders - Use lowercase with hyphens:
/blog-posts, not/blogPosts - Never expose database IDs if they reveal sensitive info — use UUIDs
- Keep nesting shallow (max 2 levels):
/users/42/ordersis fine;/users/42/orders/7/items/3/tagsis too deep
Request and Response Format
Most REST APIs use JSON. Always set Content-Type and Accept headers.
Request:
POST /api/users HTTP/1.1
Host: api.example.com
Content-Type: application/json
Authorization: Bearer eyJhbGci...
{
"name": "Ana Petrović",
"email": "ana@example.com"
}
Response:
HTTP/1.1 201 Created
Content-Type: application/json
Location: /api/users/43
{
"id": 43,
"name": "Ana Petrović",
"email": "ana@example.com",
"createdAt": "2026-07-13T10:00:00Z"
}
HTTP Status Codes for REST
| Code | Meaning | When to Use |
|---|---|---|
200 OK |
Success with body | GET, PUT, PATCH success |
201 Created |
Resource created | POST success; add Location header |
204 No Content |
Success, no body | DELETE success |
400 Bad Request |
Invalid input | Validation error |
401 Unauthorized |
Not authenticated | Missing or invalid token |
403 Forbidden |
Authenticated but no permission | Access denied |
404 Not Found |
Resource doesn't exist | Wrong ID or URL |
409 Conflict |
State conflict | Duplicate email, optimistic lock failure |
422 Unprocessable Entity |
Semantic validation error | Valid JSON but invalid business rule |
429 Too Many Requests |
Rate limit exceeded | Add Retry-After header |
500 Internal Server Error |
Server bug | Never expose stack traces |
Consuming a REST API
JavaScript (fetch)
// GET
const res = await fetch('https://api.example.com/users/42', {
headers: { Authorization: 'Bearer ' + token },
});
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const user = await res.json();
// POST
const res = await fetch('https://api.example.com/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: 'Bearer ' + token,
},
body: JSON.stringify({ name: 'Ana', email: 'ana@example.com' }),
});
const created = await res.json();
console.log(created.id); // 43
Python (requests)
import requests
BASE = "https://api.example.com"
headers = {"Authorization": "Bearer " + token}
# GET
r = requests.get(f"{BASE}/users/42", headers=headers)
r.raise_for_status() # raises on 4xx/5xx
user = r.json()
# POST
r = requests.post(
f"{BASE}/users",
headers=headers,
json={"name": "Ana", "email": "ana@example.com"}, # sets Content-Type automatically
)
r.raise_for_status()
print(r.json()["id"]) # 43
Go (net/http)
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
)
func main() {
// GET
req, _ := http.NewRequest("GET", "https://api.example.com/users/42", nil)
req.Header.Set("Authorization", "Bearer "+token)
resp, err := http.DefaultClient.Do(req)
if err != nil { panic(err) }
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Println(string(body))
// POST
payload, _ := json.Marshal(map[string]string{
"name": "Ana",
"email": "ana@example.com",
})
req2, _ := http.NewRequest("POST", "https://api.example.com/users", bytes.NewReader(payload))
req2.Header.Set("Content-Type", "application/json")
req2.Header.Set("Authorization", "Bearer "+token)
resp2, _ := http.DefaultClient.Do(req2)
defer resp2.Body.Close()
fmt.Println(resp2.StatusCode) // 201
}
PHP (cURL)
// GET
$ch = curl_init("https://api.example.com/users/42");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ["Authorization: Bearer $token"],
]);
$body = curl_exec($ch);
$user = json_decode($body, true);
curl_close($ch);
// POST
$ch = curl_init("https://api.example.com/users");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode(["name" => "Ana", "email" => "ana@example.com"]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"Authorization: Bearer $token",
],
]);
$result = json_decode(curl_exec($ch), true);
curl_close($ch);
echo $result["id"]; // 43
Authentication Patterns
API Key (simplest)
GET /api/data HTTP/1.1
X-API-Key: sk_live_abc123
Good for: server-to-server, public APIs. Bad for: browser apps (key is exposed).
Bearer Token (most common)
GET /api/profile HTTP/1.1
Authorization: Bearer eyJhbGciOiJSUzI1NiJ9...
Token is usually a JWT. Server validates the signature — no database lookup needed.
OAuth 2.0 (delegated access)
Used when your app needs to act on behalf of a user (e.g., "Sign in with Google"). Flow:
- Redirect user to authorization server
- User grants permission
- Your app receives an authorization code
- Exchange code for access token
- Use access token to call the API
Basic Auth (avoid in production)
Authorization: Basic dXNlcjpwYXNz # base64("user:pass")
Only acceptable over HTTPS, for internal tools or development.
Pagination
Never return unlimited records. Three common patterns:
Offset pagination (simple, but slow on large datasets):
GET /api/products?page=2&limit=20
{
"data": [...],
"total": 500,
"page": 2,
"limit": 20,
"totalPages": 25
}
Cursor pagination (efficient for large datasets, works with real-time data):
GET /api/feed?cursor=eyJpZCI6MTIzfQ&limit=20
{
"data": [...],
"nextCursor": "eyJpZCI6MTQzfQ",
"hasMore": true
}
Keyset pagination (best performance — uses indexed column):
GET /api/events?after_id=143&limit=20
Quick Reference
| Task | Method | URL | Response Code |
|---|---|---|---|
| List all users | GET |
/users |
200 |
| Get user by ID | GET |
/users/42 |
200 or 404 |
| Create user | POST |
/users |
201 + Location header |
| Replace user | PUT |
/users/42 |
200 or 204 |
| Update field | PATCH |
/users/42 |
200 or 204 |
| Delete user | DELETE |
/users/42 |
204 or 404 |
| List user's orders | GET |
/users/42/orders |
200 |
| Search users | GET |
/users?q=ana&role=admin |
200 |
6 Common REST API Mistakes
Using verbs in URLs (
/getUser,/deletePost) — the HTTP method is the verb; the URL is the noun.Returning 200 for errors — always use the correct status code.
200with{ "error": "not found" }is wrong; use404.No versioning — when you break an existing contract, bump the version:
/api/v1/,/api/v2/. Add it from day one.Not validating input — never trust client data. Validate all fields server-side, return
400/422with clear error messages:{ "errors": [{ "field": "email", "message": "Invalid email format" }] }Exposing database errors — never return raw SQL errors or stack traces to the client. Log server-side, return generic
500.No rate limiting — every public API endpoint needs rate limiting. Return
429 Too Many Requestswith aRetry-Afterheader.
6 FAQ
Q: What's the difference between REST and SOAP? SOAP is a strict protocol using XML envelopes with a specific message format. REST is an architectural style that's flexible, uses JSON, and relies on HTTP natively. REST is simpler and dominates modern web APIs.
Q: What's the difference between REST and GraphQL? REST has fixed endpoints per resource; GraphQL has one endpoint where you specify exactly what data you need. GraphQL eliminates over-fetching and under-fetching but adds complexity. Use REST for standard CRUD APIs, GraphQL for complex, data-intensive frontends.
Q: Should I use PUT or PATCH?
Use PUT to replace a resource entirely (send all fields). Use PATCH to update only specific fields (send only changed fields). In practice, PATCH is more common and safer.
Q: Where should I put the API version — URL or header?
URL versioning (/api/v2/) is the most common and easiest to work with (visible in logs, bookmarkable, testable in a browser). Header versioning (Accept: application/vnd.api+json;version=2) is more "pure REST" but harder to use. Pick URL versioning unless you have a specific reason not to.
Q: Is REST the same as HTTP? No. HTTP is the protocol (the transport layer). REST is an architectural style that uses HTTP. You could theoretically implement REST over other protocols, but HTTP is universal in practice.
Q: How do I document a REST API?
Use OpenAPI (formerly Swagger). Write a openapi.yaml spec describing all endpoints, parameters, request/response schemas, and authentication. Tools like Swagger UI, Redoc, and Stoplight generate interactive docs from the spec automatically.