Skip to main content

REST Fundamentals

REST (Representational State Transfer) is the most common API style on the web. Understanding its conventions makes you fluent with any third-party API and helps you design good APIs yourself (you'll build them in Module 09).

HTTP Methods

MethodMeaningHas Body
GETRetrieve dataNo
POSTCreate a resourceYes
PUTReplace a resource entirelyYes
PATCHUpdate specific fieldsYes
DELETEDelete a resourceNo

Status Codes

2xx — Success
200 OK — successful GET, PUT, PATCH, DELETE
201 Created — successful POST (resource created)
204 No Content — success with no response body

3xx — Redirect
301 Moved Permanently
302 Found (temporary redirect)

4xx — Client Error (your mistake)
400 Bad Request — invalid request body
401 Unauthorized — not logged in
403 Forbidden — logged in but not allowed
404 Not Found — resource doesn't exist
409 Conflict — duplicate resource
422 Unprocessable — validation failed
429 Too Many Requests — rate limited

5xx — Server Error (their mistake)
500 Internal Server Error
502 Bad Gateway
503 Service Unavailable

REST URL Conventions

Collection:   GET    /api/users           — list all users
Single: GET /api/users/:id — get user by ID
Create: POST /api/users — create new user
Update: PATCH /api/users/:id — update specific fields
Replace: PUT /api/users/:id — replace entire resource
Delete: DELETE /api/users/:id — delete user

Nested: GET /api/users/:id/posts — user's posts
Action: POST /api/users/:id/follow— follow a user

Query Parameters

Used for filtering, sorting, pagination, and search:

GET /api/products?category=electronics
GET /api/products?minPrice=100&maxPrice=500
GET /api/products?sort=price&order=asc
GET /api/products?page=2&limit=20
GET /api/products?q=macbook+pro
GET /api/products?include=reviews,images
// Building query strings in TypeScript
function buildQueryString(params: Record<string, string | number | boolean | undefined>): string {
const filtered = Object.entries(params)
.filter(([, v]) => v !== undefined && v !== '')
.map(([k, v]) => [k, String(v)]);

return new URLSearchParams(filtered).toString();
}

const qs = buildQueryString({ category: 'electronics', minPrice: 100, page: 2 });
// 'category=electronics&minPrice=100&page=2'

const url = `/api/products?${qs}`;

Request and Response Headers

// Common request headers
const headers = {
'Content-Type': 'application/json', // body format
'Authorization': `Bearer ${token}`, // auth
'Accept': 'application/json', // expected response format
'X-Request-ID': crypto.randomUUID(), // request tracing
};

// Reading response headers
const res = await fetch('/api/data');
res.headers.get('Content-Type'); // 'application/json; charset=utf-8'
res.headers.get('X-Rate-Limit-Remaining'); // '95'
res.headers.get('X-Total-Count'); // '1234' (pagination total)

Pagination Patterns

// Offset pagination
GET /api/posts?page=3&limit=20
// Response:
{
"data": [...],
"pagination": {
"page": 3,
"limit": 20,
"total": 150,
"totalPages": 8
}
}

// Cursor pagination (more scalable)
GET /api/posts?cursor=eyJpZCI6MTAwfQ&limit=20
// Response:
{
"data": [...],
"nextCursor": "eyJpZCI6MTIwfQ",
"hasMore": true
}
// Loading all pages
async function fetchAllPages<T>(baseUrl: string): Promise<T[]> {
const results: T[] = [];
let page = 1;
let hasMore = true;

while (hasMore) {
const res = await api.get<{ data: T[]; pagination: { totalPages: number } }>(
`${baseUrl}?page=${page}&limit=100`
);
results.push(...res.data);
hasMore = page < res.pagination.totalPages;
page++;
}

return results;
}