APIs lessons
What is an API
Restaurant analogy illustration: client, API and server with request/response arrows.
An API (Application Programming Interface) is a set of rules that lets one program talk to another. It defines what you can ask for, how to ask, and what you get back.
Think of a restaurant: you (the client) give your order to a waiter (the API), who takes it to the kitchen (the server) and brings back your food (the response).
APIs power everything from weather apps to payment systems. Any time two pieces of software exchange data, an API is involved.
How APIs connect systems
A client sends a request to a server. The server processes it and sends back a response.
The client never sees the server's internal code or database. It only interacts through the API's defined endpoints.
[ Client (Browser) ] ----> [ API Server ] ----> [ Database ]
<---- <----
Response Query ResultPractice: Match the analogy
Match each API concept to its restaurant analogy.
HTTP methods
HTTP methods tell the server what action to perform. The four main methods map to CRUD operations: Create, Read, Update, Delete.
GET reads data, POST creates data, PUT updates data, and DELETE removes data.
GET /users → Read all users
POST /users → Create a new user
PUT /users/5 → Update user with id 5
DELETE /users/5 → Delete user with id 5HTTP method comparison
Each method has a specific purpose. GET and DELETE typically have no request body, while POST and PUT carry data in the body.
GET requests are idempotent, meaning calling them multiple times produces the same result. POST is not idempotent since each call can create a new resource.
Method Has Body? Idempotent? Purpose
────── ───────── ─────────── ────────────────
GET No Yes Read / Fetch
POST Yes No Create
PUT Yes Yes Update / Replace
DELETE No Yes RemovePractice: Pick the right HTTP method
Pick the correct HTTP method for each scenario.
URLs, endpoints, and routes
An API endpoint is a specific URL that accepts requests. The base URL is the server address, and the path identifies the resource.
Routes are the path patterns defined on the server. For example, /api/users and /api/products are two different routes.
https://api.example.com/v1/users/42
└─── base URL ────────┘└── path ──┘
└ resource ┘└ id ┘Query and path parameters
Path parameters are part of the URL itself, like /users/42 where 42 is the user ID. They identify a specific resource.
Query parameters come after a ? and filter or modify the request. Multiple parameters are separated by &.
# Path parameter — specific user
GET /api/users/42
# Query parameters — filter and sort
GET /api/users?role=admin&sort=name
# Combined — user 42's posts, page 2
GET /api/users/42/posts?page=2&limit=10Practice: Build a URL
Assemble URL parts in the correct order to build a valid endpoint.
Request structure
Every HTTP request has three parts: a request line (method + URL), headers, and an optional body.
Headers carry metadata like Content-Type and Authorization. The body carries the data you are sending to the server.
POST /api/users HTTP/1.1
Host: api.example.com
Content-Type: application/json
Authorization: Bearer abc123
{
"name": "Alice",
"email": "alice@example.com"
}Response structure
A response includes a status line, headers, and a body. The status line contains the HTTP version, a status code, and a reason phrase.
The Content-Type header tells the client what format the body is in. Most APIs return application/json.
HTTP/1.1 201 Created
Content-Type: application/json
{
"id": 7,
"name": "Alice",
"email": "alice@example.com"
}Practice: Label request and response parts
Label the parts of a raw HTTP request and response.
JSON as the data format
JSON (JavaScript Object Notation) is the standard format for API data. It uses key-value pairs inside curly braces, and arrays inside square brackets.
Keys must be strings in double quotes. Values can be strings, numbers, booleans, arrays, objects, or null.
{
"user": {
"id": 1,
"name": "Alice",
"roles": ["admin", "editor"],
"active": true,
"address": null
}
}Reading nested JSON
Access nested values using dot notation or bracket notation. For example, data.user.name returns "Alice".
Array items use a zero-based index. data.user.roles[0] returns "admin".
const data = await response.json();
console.log(data.user.name); // "Alice"
console.log(data.user.roles[0]); // "admin"
console.log(data.user.active); // truePractice: Extract values from JSON
Type the dot/bracket notation to extract a value from JSON.
Status codes overview
Status codes are three-digit numbers grouped by category. The first digit tells you the general outcome of the request.
2xx means success, 4xx means the client made an error, and 5xx means the server failed.
Category Meaning Examples
──────── ────────────────── ────────────────────────
2xx Success 200 OK, 201 Created
3xx Redirection 301 Moved, 304 Not Modified
4xx Client Error 400 Bad Request, 404 Not Found
5xx Server Error 500 Internal, 503 UnavailableCommon status codes
200 is the standard success response. 201 means a resource was created. 204 means success with no content returned.
400 means the request was malformed. 401 means not authenticated, and 403 means authenticated but not authorized. 404 means the resource was not found.
// Success responses
200 OK — Request succeeded
201 Created — New resource created
204 No Content — Success, nothing to return
// Client errors
400 Bad Request — Invalid syntax or data
401 Unauthorized — No valid credentials
403 Forbidden — Credentials valid, access denied
404 Not Found — Resource does not exist
// Server errors
500 Internal Error — Server crashed or threw
503 Unavailable — Server temporarily downPractice: Complete the status code table
Fill in the missing status codes and reason phrases.
REST principles
REST (Representational State Transfer) is a set of guidelines for designing APIs. A RESTful API uses HTTP methods on resources identified by URLs.
Key constraints include: statelessness (each request carries all needed info), uniform interface (consistent URL patterns), and resource-based design (URLs represent nouns, not verbs).
# RESTful (resources as nouns)
GET /api/posts — list posts
GET /api/posts/7 — get post 7
POST /api/posts — create a post
# Not RESTful (verbs in URL)
GET /api/getPosts
POST /api/createPost
POST /api/deletePost/7Designing RESTful URLs
Use plural nouns for collections: /users, not /user. Nest related resources: /users/5/posts for posts belonging to user 5.
Keep URLs predictable and consistent. Avoid deep nesting beyond two levels. Use query parameters for filtering and sorting instead of inventing new paths.
# Good REST design
GET /api/users — all users
GET /api/users/5 — user 5
GET /api/users/5/posts — user 5's posts
GET /api/posts?author=5 — alternative: filter posts
# Avoid
GET /api/getUserById/5
GET /api/users/5/posts/comments/likes — too deepPractice: RESTful or not
Decide whether each method + URL follows REST principles.
Authentication basics
Most APIs require authentication to identify who is making a request. The two most common methods are API keys and bearer tokens.
API keys are static strings included in the header or query string. Bearer tokens (like JWTs) are issued after login and sent in the Authorization header.
# API key in header
GET /api/weather?city=London
X-API-Key: sk_abc123def456
# Bearer token in header
GET /api/profile
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...API keys vs tokens
API keys identify the application. They are long-lived and usually scoped to a project. Tokens identify the user and typically expire after a set time.
Never expose API keys in client-side code or commit them to version control. Store them in environment variables and access them on the server only.
// Environment variable on the server
const apiKey = process.env.WEATHER_API_KEY;
// Use in server-side fetch
const res = await fetch("https://api.weather.com/current?city=London", {
headers: { "X-API-Key": apiKey }
});Practice: Spot the security mistake
Identify which code snippet exposes an API key insecurely.
Error handling
Good APIs return consistent error responses with a status code, an error message, and optionally a machine-readable error code.
On the client side, always check response.ok or the status code before parsing the body. Wrap API calls in try/catch to handle network failures.
// Server error response
HTTP/1.1 422 Unprocessable Entity
{
"error": "VALIDATION_FAILED",
"message": "Email is required",
"field": "email"
}
// Client-side handling
try {
const res = await fetch("/api/users", { method: "POST", body });
if (!res.ok) {
const err = await res.json();
console.error(err.message);
}
} catch (e) {
console.error("Network error");
}Practice: Handle the error
Fill in the blanks to add error handling to a fetch call.
Building an endpoint with Express
Express is a Node.js framework for building APIs. You define routes using app.get(), app.post(), and similar methods.
Each route handler receives a req (request) and res (response) object. Use res.json() to send JSON back to the client.
const express = require("express");
const app = express();
app.use(express.json());
const users = [
{ id: 1, name: "Alice" },
{ id: 2, name: "Bob" }
];
app.get("/api/users", (req, res) => {
res.json(users);
});
app.get("/api/users/:id", (req, res) => {
const user = users.find(u => u.id === Number(req.params.id));
if (!user) return res.status(404).json({ error: "User not found" });
res.json(user);
});
app.listen(3000, () => console.log("Server running on port 3000"));Adding POST and DELETE routes
A POST route reads data from req.body and creates a resource. A DELETE route finds the resource by ID and removes it.
Always validate input before processing and return appropriate status codes. Return 201 for successful creation and 204 for successful deletion.
app.post("/api/users", (req, res) => {
const { name } = req.body;
if (!name) return res.status(400).json({ error: "Name is required" });
const newUser = { id: users.length + 1, name };
users.push(newUser);
res.status(201).json(newUser);
});
app.delete("/api/users/:id", (req, res) => {
const index = users.findIndex(u => u.id === Number(req.params.id));
if (index === -1) return res.status(404).json({ error: "User not found" });
users.splice(index, 1);
res.status(204).send();
});Practice: Write a GET endpoint
Write an Express GET route handler that finds a product or returns 404.
Making requests with fetch
The fetch() function is built into modern browsers and Node.js 18+. It returns a Promise that resolves to a Response object.
For GET requests, just pass the URL. For POST, add a second argument with method, headers, and body.
// GET request
const res = await fetch("/api/users");
const users = await res.json();
// POST request
const res = await fetch("/api/users", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ name: "Charlie" })
});
const newUser = await res.json();Practice: Write a fetch call
Write a fetch POST request that sends a JSON body and logs the result.
Putting it all together
A complete API flow starts with the client building a request (method, URL, headers, body), sending it to the server, and the server routing it to the correct handler.
The handler processes the request, interacts with a database if needed, and returns a response with the right status code and JSON body. The client then parses and uses that data.
// Full round trip
// 1. Client sends POST request
const res = await fetch("/api/posts", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer " + token
},
body: JSON.stringify({ title: "Hello", content: "World" })
});
// 2. Server handles it
app.post("/api/posts", auth, (req, res) => {
const post = db.create({ ...req.body, author: req.user.id });
res.status(201).json(post);
});
// 3. Client uses the response
if (res.ok) {
const post = await res.json();
console.log("Created:", post.title);
}