Loading
Understand REST APIs, make HTTP requests, handle responses, and build your own API endpoints.
An API (Application Programming Interface) is a contract between two pieces of software. When you hear "API" in web development, it usually means REST API — a server that accepts HTTP requests and returns data (usually JSON).
Real-world example: When a weather app shows today's forecast, it's calling a weather API. Your app sends "give me weather for New York" and the API responds with temperature, humidity, etc.
APIs are how applications talk to each other.
Every API request uses an HTTP method that describes what you want to do:
| Method | Purpose | Example | | ---------- | -------------- | ---------------------------- | | GET | Read data | Fetch a list of users | | POST | Create data | Create a new user | | PUT | Replace data | Update an entire user record | | PATCH | Partial update | Change just the user's email | | DELETE | Remove data | Delete a user |
Status codes tell you what happened:
| Range | Meaning | Common Codes | | ----- | ------------ | -------------------------------------------------- | | 2xx | Success | 200 OK, 201 Created, 204 No Content | | 4xx | Client error | 400 Bad Request, 401 Unauthorized, 404 Not Found | | 5xx | Server error | 500 Internal Server Error, 503 Service Unavailable |
Each method has a specific purpose. Using the right one matters.
JavaScript's built-in fetch API is all you need:
Key patterns:
response.ok before parsing the bodyJSON.stringify() for request bodiesContent-Type: application/json for POST/PUT/PATCHfetch() is the modern way to make HTTP requests in JavaScript.
Next.js lets you create API endpoints right in your project:
File-based routing: The file path becomes the URL:
src/app/api/users/route.ts → GET /api/userssrc/app/api/users/[id]/route.ts → GET /api/users/123Next.js makes building APIs simple with file-based routing.
Robust API consumption requires good error handling:
Common patterns:
Good error handling is the difference between a demo and production code.
Most APIs require authentication. Common methods:
API Key (simplest):
Bearer Token (most common for user auth):
Never expose API keys in client-side code. Use Next.js API routes as a proxy:
Keep secrets on the server. Always.
When building your own APIs:
| Practice | Example |
| ------------------------------- | -------------------------------------------------- |
| Use nouns for resources | /api/users not /api/getUsers |
| Use HTTP methods for actions | DELETE /api/users/123 not POST /api/deleteUser |
| Return appropriate status codes | 201 for created, 404 for not found |
| Validate input | Check required fields, types, ranges |
| Use consistent error format | { error: "message", code: "VALIDATION_ERROR" } |
| Paginate large collections | ?page=1&limit=20 |
| Version your API | /api/v1/users |
Good API design makes your code easier to use and maintain.
What's next? Learn How to Prepare for Technical Interviews to turn your skills into career opportunities.
Your App → HTTP Request → API Server → Database
Your App ← JSON Response ← API Server ← Database// GET request — fetch data
async function getUsers() {
const response = await fetch("https://api.example.com/users");
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const users = await response.json();
return users;
}
// POST request — send data
async function createUser(name, email) {
const response = await fetch("https://api.example.com/users", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ name, email }),
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.message);
}
return response.json();
}// src/app/api/users/route.ts
import { NextResponse } from "next/server";
// Handle GET /api/users
export async function GET(): Promise<NextResponse> {
const users = [
{ id: 1, name: "Alice", email: "alice@example.com" },
{ id: 2, name: "Bob", email: "bob@example.com" },
];
return NextResponse.json(users);
}
// Handle POST /api/users
export async function POST(request: Request): Promise<NextResponse> {
const body = await request.json();
if (!body.name || !body.email) {
return NextResponse.json({ error: "Name and email are required" }, { status: 400 });
}
// In a real app, save to database here
const newUser = { id: Date.now(), ...body };
return NextResponse.json(newUser, { status: 201 });
}async function fetchWithRetry(url: string, options?: RequestInit, retries = 3): Promise<Response> {
for (let attempt = 1; attempt <= retries; attempt++) {
try {
const response = await fetch(url, options);
if (response.status === 429) {
// Rate limited — wait and retry
const retryAfter = Number(response.headers.get("Retry-After")) || 1;
await new Promise((r) => setTimeout(r, retryAfter * 1000));
continue;
}
return response;
} catch (error) {
if (attempt === retries) throw error;
// Network error — wait briefly and retry
await new Promise((r) => setTimeout(r, 1000 * attempt));
}
}
throw new Error("Max retries reached");
}// Cancel a request after 5 seconds
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 5000);
const response = await fetch(url, { signal: controller.signal });
clearTimeout(timeout);const response = await fetch("https://api.example.com/data", {
headers: { "X-API-Key": "your-api-key-here" },
});const response = await fetch("https://api.example.com/profile", {
headers: { Authorization: `Bearer ${accessToken}` },
});// src/app/api/weather/route.ts — server-side, key is safe
export async function GET(request: Request): Promise<NextResponse> {
const { searchParams } = new URL(request.url);
const city = searchParams.get("city");
// API key is in server-side env variable — never sent to browser
const response = await fetch(
`https://weather-api.com/data?city=${city}&key=${process.env.WEATHER_API_KEY}`
);
const data = await response.json();
return NextResponse.json(data);
}