Building Your First REST API with Node.js and Express
A beginner-friendly walkthrough of building a real REST API with Node.js and Express in 2026. Learn routing, request handling, JSON responses, error handling, and how to test your endpoints with curl and Postman.
A REST API is just a web server that speaks JSON over HTTP. Once you can build one, the entire backend half of the web opens up — your front end can fetch data, your mobile app can sync, and other services can integrate with what you built. The fastest, friendliest path in the Node.js ecosystem is still Express, which shipped its long-awaited 5.0 stable release in October 2024 and is the framework most tutorials, jobs, and Stack Overflow answers assume in 2026.
This guide walks through building a real REST API for a tiny "books" resource — list, get, create, update, delete — using Express 5. No database, no auth (yet); just the routing, middleware, and request-handling foundations that every Node backend builds on.
What "REST API" Actually Means
REST is a convention for designing HTTP APIs around resources. A resource is a noun (books, users, orders) and the HTTP method is the verb you apply to it.
| Method | Path | What it does |
|---|---|---|
| GET | /books | List all books |
| GET | /books/:id | Get one book |
| POST | /books | Create a book |
| PUT | /books/:id | Replace a book |
| PATCH | /books/:id | Update some fields |
| DELETE | /books/:id | Delete a book |
Status codes carry meaning: 200 OK, 201 Created, 204 No Content, 400 Bad Request, 404 Not Found, 500 Internal Server Error. Stick to the conventions and any client (browser, mobile, another service) can consume your API without surprises.
Setting Up the Project
You need Node.js 22 LTS or newer. In a fresh folder:
mkdir books-api && cd books-api
npm init -y
npm pkg set type=module
npm install expressSetting "type": "module" lets you use modern import/export instead of require. Open the folder in VS Code and create server.js.
The Minimal Express Server
// server.js
import express from "express";
const app = express();
app.use(express.json());
app.get("/", (_req, res) => {
res.json({ status: "ok" });
});
app.listen(3000, () => console.log("API on http://localhost:3000"));Run node server.js and hit http://localhost:3000 in your browser. You should see {"status":"ok"}. That is a real HTTP server in nine lines.
Two things to notice. app.use(express.json()) is middleware — it runs on every request and parses JSON bodies into req.body. Without it, POST requests arrive empty. Express 5 made async middleware fully supported, so you can await inside route handlers without any extra wrapping.
Building the Books Resource
We will keep books in memory for now. Replace your file with this:
import express from "express";
import { randomUUID } from "node:crypto";
const app = express();
app.use(express.json());
const books = new Map();
// list
app.get("/books", (_req, res) => {
res.json([...books.values()]);
});
// get one
app.get("/books/:id", (req, res) => {
const book = books.get(req.params.id);
if (!book) return res.status(404).json({ error: "Not found" });
res.json(book);
});The pattern is consistent: pick a method (get, post, put, delete), give it a path, and a handler that gets req and res. The path can include :params that show up on req.params. Query strings (?q=node) come in as req.query. JSON bodies come in as req.body because of the JSON middleware.
Create, Update, Delete
Add the rest of the CRUD operations below the read handlers:
app.post("/books", (req, res) => {
const { title, author } = req.body ?? {};
if (!title || !author) {
return res.status(400).json({ error: "title and author are required" });
}
const book = { id: randomUUID(), title, author };
books.set(book.id, book);
res.status(201).json(book);
});
app.delete("/books/:id", (req, res) => {
if (!books.delete(req.params.id)) {
return res.status(404).json({ error: "Not found" });
}
res.status(204).end();
});
app.listen(3000, () => console.log("API on http://localhost:3000"));Test it from a second terminal with curl:
curl -X POST http://localhost:3000/books \
-H "content-type: application/json" \
-d '{"title":"Dune","author":"Frank Herbert"}'
curl http://localhost:3000/booksYou now have a working REST API. The same shape — middleware, routes, handlers that read req and write res — scales to a full production app.
Middleware: The Real Power of Express
Middleware are functions that run between the request arriving and your handler responding. They share one signature: (req, res, next). Call next() to pass control to the next middleware.
app.use((req, _res, next) => {
console.log(`${req.method} ${req.url}`);
next();
});That is a request logger. Real apps stack many: cors() for CORS, helmet() for security headers, an auth middleware that verifies a JWT and attaches req.user, a rate limiter, an error handler at the very end. Express 5 supports async middleware that throws — errors are forwarded to error handlers automatically, no try/catch wrapper needed.
What Comes Next
In a real project the in-memory Map is replaced by a database (Postgres with Drizzle or Prisma, or MongoDB), and you split routes into separate files using express.Router(). Add validation with Zod, authentication with JWTs or sessions, and tests with Vitest. Each piece slots into the same middleware-and-handler model you already built.
For pure Node.js fundamentals before you go deeper, see What is Node.js?.
Common Mistakes Beginners Make
- Forgetting
app.use(express.json())and being confused whyreq.bodyis undefined. - Returning the wrong status code (200 for a created resource — should be 201).
- Putting business logic directly in route handlers. Extract it into functions you can test in isolation.
- Letting errors crash the server. Add an error-handling middleware:
app.use((err, req, res, next) => { ... }). - Hard-coding the port. Use
process.env.PORT ?? 3000so deployment platforms can override it.
Quick Reference
- Install:
npm install express - Module mode:
npm pkg set type=module - Routes:
app.get,app.post,app.put,app.patch,app.delete - Path params:
:id→req.params.id. Query:req.query. Body:req.body - JSON body parser:
app.use(express.json()) - Status codes: 200, 201 created, 204 no content, 400, 404, 500
- Middleware shape:
(req, res, next) => { ... next(); } - Group routes:
const r = express.Router();thenapp.use("/books", r); - Hot reload in dev:
node --watch server.js(built into Node 18+)
Rune AI
Key Insights
- REST means HTTP methods (GET, POST, PUT, PATCH, DELETE) applied to resource URLs that return JSON.
- Express 5 (stable since 2024) is the default beginner-friendly framework for Node.js APIs in 2026.
app.use(express.json())is required to parse JSON request bodies.- Use the right status codes: 200, 201, 204, 400, 404, 500 — clients depend on them.
- Middleware is the extension point: logging, auth, validation, rate limiting all plug in the same way.
Frequently Asked Questions
Express vs Fastify vs Hono in 2026?
Do I need TypeScript?
Where do I store the data?
How do I deploy this?
Is Express still maintained?
Conclusion
Express turns the raw node:http module into something you actually want to use. Routes, middleware, and handlers are the only three concepts you need on day one, and the same shape carries you all the way to a production backend. Build the books API, swap the Map for Postgres, add a users resource and a JWT middleware, and you have a real backend.