Node.js Backend Fundamentals with Express · Build an API with Express
Your First Express API
Create an Express 5 app with JSON routes, start it, and call it, seeing exactly what the server sends back.
What you'll build
A small "books" API with GET /books, GET /books/:id and POST /books, built with Express 5 and Node.js 24. Every example in this course starts the app on a free port, sends real HTTP requests with fetch and prints the responses.
The idea
express()creates an app;app.get(path, handler)andapp.post(...)register routes.- A handler receives the request (
req.params,req.query,req.body) and the response (res.json(),res.status()). express.json()is middleware that parses JSON request bodies intoreq.body.app.listen(port)starts an HTTP server. In real apps the port comes from configuration (process.env.PORT).- Express 5 (the current major version) handles promises returned from async handlers: a thrown error reaches your error handler automatically.
The code
import express from "express";
const books = [
{ id: 1, title: "Clean Code", author: "Robert C. Martin" },
{ id: 2, title: "Refactoring", author: "Martin Fowler" },
];
const app = express();
app.use(express.json());
app.get("/books", (req, res) => res.json(books));
app.get("/books/:id", (req, res) => {
const book = books.find((b) => b.id === Number(req.params.id));
if (!book) return res.status(404).json({ error: "book not found" });
res.json(book);
});
app.post("/books", (req, res) => {
const book = { id: books.length + 1, title: req.body.title, author: req.body.author };
books.push(book);
res.status(201).location(`/books/${book.id}`).json(book);
});
// start on a free port and call the API (in production: app.listen(process.env.PORT ?? 3000))
const server = app.listen(0);
const base = `http://127.0.0.1:${server.address().port}`;
const show = async (r) => console.log(r.status, JSON.stringify(await r.json()));
await show(await fetch(`${base}/books/2`));
await show(await fetch(`${base}/books/9`));
const created = await fetch(`${base}/books`, {
method: "POST", headers: { "Content-Type": "application/json" },
body: JSON.stringify({ title: "The Pragmatic Programmer", author: "Hunt and Thomas" }),
});
console.log("Location:", created.headers.get("location"));
await show(created);
server.close();
Output
200 {"id":2,"title":"Refactoring","author":"Martin Fowler"}
404 {"error":"book not found"}
Location: /books/3
201 {"id":3,"title":"The Pragmatic Programmer","author":"Hunt and Thomas"}Run this on your computer — see “Get Started” for setup.
Common mistakes
- Forgetting
app.use(express.json()), soreq.bodyisundefined. - Comparing
req.params.id(a string) with a number without converting it. - Returning 200 for everything; use 201 for created resources and 404 when something doesn't exist.
- Calling
res.json()twice in one handler (Cannot set headers after they are sent):returnafter responding early.
Key points
- Routes map an HTTP method and path to a handler.
express.json()parses JSON bodies;res.status().json()sends responses.- Express 5 forwards errors from async handlers to error middleware.
Further reading: Express 5 routing · Migrating to Express 5
Practice questions
What does app.use(express.json()) do?
Answer
It registers middleware that parses requests with a JSON body and puts the result in req.body; without it req.body is undefined.
Why compare Number(req.params.id) rather than req.params.id with a numeric id?
Answer
Route parameters are always strings; comparing a string with a number using === is always false.
What changed for async handlers in Express 5?
Answer
Rejected promises and errors thrown in async handlers are passed to next(err) automatically, so they reach error-handling middleware without try/catch wrappers.
Open in the app to mark lessons complete, take notes and track your progress.