APIs — how apps talk to each other
APIs are one of the most important concepts in modern web development — and one of the least explained. Every app you've ever used talks to dozens of APIs. Here's exactly how they work.
What is an API?
API stands for Application Programming Interface. That's a mouthful. Here's what it actually means:
An API is a way for one program to ask another program for something. When your weather app shows the temperature, it didn't calculate that itself — it asked a weather API. When you log in with Google, your app talks to Google's API. When you pay with Stripe, your app talks to Stripe's API.
Think of it like a waiter at a restaurant. You (the client) don't go into the kitchen yourself. You tell the waiter (the API) what you want, the kitchen (the server) prepares it, and the waiter brings it back to you. The API is the defined interface between you and the kitchen.
Requests and responses
Every API interaction is a request followed by a response. The client asks, the server answers.
// Ask the GitHub API for info about a user
const response = await fetch('https://api.github.com/users/webgradeteam-png');
const data = await response.json();
console.log(data.name); // "Webgrade"
console.log(data.public_repos); // 12The fetch() function sends an HTTP request to the URL. The server responds with JSON data. response.json() parses that text into a JavaScript object you can work with.
JSON — the language of APIs
Most modern APIs send and receive data in JSON (JavaScript Object Notation). It looks like a JavaScript object but it's just text — every language can read it.
{
"id": 42,
"name": "Alex Johnson",
"email": "alex@example.com",
"role": "admin",
"active": true,
"createdAt": "2024-01-15T10:30:00Z",
"tags": ["developer", "beta-user"]
}Keys are always strings in double quotes. Values can be strings, numbers, booleans (true/false), arrays [], nested objects {}, or null. No trailing commas. No comments.
HTTP methods
APIs use HTTP methods to indicate what operation you want to perform:
Fetch data. Never changes anything. Safe to call multiple times. Used for reading users, posts, products.
Create something new. Sends data in the request body. Creates a new user, new order, new post.
Replace a resource entirely. Send the full updated object.
Update part of a resource. Only send the fields you're changing.
Remove a resource. Usually no body needed — just the ID in the URL.
Status codes
Every response includes a status code telling you what happened:
Headers — metadata for every request
HTTP headers are key-value pairs sent alongside a request or response. They carry metadata — information about the request, authentication, content type, and more.
const response = await fetch('https://api.example.com/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json', // "I'm sending JSON"
'Authorization': `Bearer ${token}`, // "Here's my auth token"
'Accept': 'application/json', // "Give me JSON back"
'X-API-Key': process.env.API_KEY // Some APIs use custom headers
},
body: JSON.stringify({ name: 'Alex', email: 'alex@example.com' })
});Authentication
Most APIs require you to prove who you are. There are several common patterns:
headers: { 'Authorization': 'Bearer YOUR_TOKEN_HERE' }headers: { 'X-API-Key': 'YOUR_KEY_HERE' }headers: { 'Authorization': 'Basic ' + btoa('username:password') }Error handling
fetch() only throws an error if the network request fails entirely. A 404 or 500 response does NOT throw — you have to check the status code yourself.
async function getUser(id) {
try {
const res = await fetch(`/api/users/${id}`);
// fetch doesn't throw on 4xx/5xx — check manually
if (!res.ok) {
throw new Error(`API error: ${res.status}`);
}
return await res.json();
} catch (err) {
console.error('Request failed:', err.message);
throw err; // re-throw so the caller can handle it
}
}Rate limiting
APIs limit how many requests you can make to prevent abuse. When you exceed the limit, you get a 429 Too Many Requests response. Most APIs tell you your limit in response headers:
X-RateLimit-Limit: 100 # Total requests allowed per window
X-RateLimit-Remaining: 7 # Requests left in the current window
X-RateLimit-Reset: 1735689600 # Unix timestamp when the window resets
Retry-After: 60 # Seconds to wait before retryingWhen you hit a rate limit: check the Retry-After header, wait that long, and try again. Never hammer an API in a loop after a 429 — you'll get blocked.
REST vs GraphQL
Most APIs are either REST or GraphQL. They solve the same problem differently:
// Get user (returns ALL user fields)
GET /api/users/42
// Get user's posts (separate request)
GET /api/users/42/posts
// Get a specific post
GET /api/posts/8// One request, get exactly what you need
POST /graphql
{
query: `{
user(id: 42) {
name
email
posts(limit: 5) {
title
publishedAt
}
}
}`
}REST is simpler, more common, and easier to cache. GraphQL is more flexible but adds complexity. For most projects, REST is the right choice. Learn REST first.
Webhooks — APIs in reverse
A regular API call is pull: you ask, they respond. A webhook is push: they call you when something happens.
When a Stripe payment succeeds, Stripe doesn't wait for you to ask — it immediately sends an HTTP POST to a URL you configure. Your server receives that request and processes the event (fulfil the order, send a confirmation email, update the database).
app.post('/webhook/stripe', express.raw({type: 'application/json'}), (req, res) => {
// Always verify the signature — proves it's really from Stripe
const sig = req.headers['stripe-signature'];
let event;
try {
event = stripe.webhooks.constructEvent(req.body, sig, process.env.STRIPE_WEBHOOK_SECRET);
} catch (err) {
return res.status(400).send(`Webhook error: ${err.message}`);
}
if (event.type === 'payment_intent.succeeded') {
// Fulfil the order
}
res.json({ received: true });
});Pagination
APIs never return unlimited data. When a list has more items than the page size, you get a page and a way to fetch the next one.
// First page
GET /api/posts?limit=20
// Response includes a cursor for the next page
{
"data": [...],
"next_cursor": "cur_abc123",
"has_more": true
}
// Next page
GET /api/posts?limit=20&after=cur_abc123