Working with APIs
Connect your app to the world. CRUD, auth, pagination, and real patterns.
REST Basics
RESTful APIs use HTTP methods on URL endpoints.
| Method | Purpose | Example |
|---|---|---|
| GET | Read | GET /api/users |
| POST | Create | POST /api/users |
| PUT | Replace | PUT /api/users/1 |
| PATCH | Update | PATCH /api/users/1 |
| DELETE | Delete | DELETE /api/users/1 |
// REST URL patterns:
// Collection: /api/users (list of users)
// Resource: /api/users/123 (single user)
// Nested: /api/users/123/posts (user's posts)
// Filtered: /api/users?role=admin&limit=10
// A REST API response typically looks like:
{
"data": [...],
"meta": { "total": 100, "page": 1 }
}CRUD Operations
Create, Read, Update, Delete — the four fundamental operations.
const API_URL = "https://api.example.com";
// CREATE — add a new resource:
async function createUser(userData) {
const res = await fetch(`${API_URL}/users`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(userData),
});
if (!res.ok) throw new Error(`Create failed: ${res.status}`);
return res.json(); // { id: 4, name: "Alice", ... }
}
// READ — get resources:
async function getUsers() {
const res = await fetch(`${API_URL}/users`);
if (!res.ok) throw new Error(`Fetch failed: ${res.status}`);
return res.json();
}
async function getUser(id) {
const res = await fetch(`${API_URL}/users/${id}`);
if (!res.ok) throw new Error(`User not found`);
return res.json();
}// UPDATE — modify existing resource:
async function updateUser(id, updates) {
const res = await fetch(`${API_URL}/users/${id}`, {
method: "PATCH",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(updates),
});
if (!res.ok) throw new Error(`Update failed: ${res.status}`);
return res.json();
}
// DELETE — remove a resource:
async function deleteUser(id) {
const res = await fetch(`${API_URL}/users/${id}`, {
method: "DELETE",
});
if (!res.ok) throw new Error(`Delete failed: ${res.status}`);
return res.status === 204 ? null : res.json();
}Authentication
Prove who you are to the API.
// Method 1: Bearer Token (most common)
async function fetchProtected(url, token) {
const res = await fetch(url, {
headers: {
"Authorization": `Bearer ${token}`,
},
});
if (res.status === 401) {
// Token expired — refresh it
const newToken = await refreshToken();
return fetchProtected(url, newToken);
}
return res.json();
}
// Method 2: API Key (simpler)
const res = await fetch(`${API_URL}/data?api_key=${API_KEY}`);
// or in headers:
const res2 = await fetch(url, {
headers: { "X-API-Key": API_KEY },
});// Login flow:
async function login(email, password) {
const res = await fetch("/api/auth/login", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ email, password }),
});
if (!res.ok) throw new Error("Invalid credentials");
const { token, refreshToken } = await res.json();
// Store securely (httpOnly cookie is best)
localStorage.setItem("token", token);
return token;
}Pagination
Handle large datasets by loading in pages.
// Offset-based pagination:
async function getUsers(page = 1, limit = 20) {
const res = await fetch(
`/api/users?page=${page}&limit=${limit}`
);
const data = await res.json();
// { users: [...], total: 150, page: 1, pages: 8 }
return data;
}
// Load all pages:
async function getAllUsers() {
const allUsers = [];
let page = 1;
let hasMore = true;
while (hasMore) {
const { users, pages } = await getUsers(page);
allUsers.push(...users);
hasMore = page < pages;
page++;
}
return allUsers;
}// Cursor-based pagination (better for real-time data):
async function fetchFeed(cursor = null) {
const url = cursor
? `/api/feed?after=${cursor}&limit=20`
: "/api/feed?limit=20";
const res = await fetch(url);
const { items, nextCursor } = await res.json();
return { items, nextCursor };
// nextCursor = null means no more items
}
// Infinite scroll:
let cursor = null;
async function loadMore() {
const { items, nextCursor } = await fetchFeed(cursor);
cursor = nextCursor;
appendToList(items);
if (!nextCursor) hideLoadMoreButton();
}Building an API Client
Wrap fetch in a reusable utility.
// A simple, reusable API client:
class ApiClient {
constructor(baseUrl, token = null) {
this.baseUrl = baseUrl;
this.token = token;
}
async request(endpoint, options = {}) {
const url = `${this.baseUrl}${endpoint}`;
const headers = {
"Content-Type": "application/json",
...(this.token && { Authorization: `Bearer ${this.token}` }),
...options.headers,
};
const res = await fetch(url, { ...options, headers });
if (!res.ok) {
const error = await res.json().catch(() => ({}));
throw new Error(error.message || `HTTP ${res.status}`);
}
if (res.status === 204) return null;
return res.json();
}
get(endpoint) {
return this.request(endpoint);
}
post(endpoint, data) {
return this.request(endpoint, {
method: "POST",
body: JSON.stringify(data),
});
}
patch(endpoint, data) {
return this.request(endpoint, {
method: "PATCH",
body: JSON.stringify(data),
});
}
delete(endpoint) {
return this.request(endpoint, { method: "DELETE" });
}
}// Usage:
const api = new ApiClient("https://api.example.com", token);
const users = await api.get("/users");
const newUser = await api.post("/users", { name: "Alice" });
await api.patch(`/users/${newUser.id}`, { name: "Bob" });
await api.delete(`/users/${newUser.id}`);FAQ
Common questions about working with APIs.