Master how the Node.js runtime and the Express 5 framework work together to power full-stack backend servers. Learn TCP port binding, RESTful routing with route parameters and query strings, body parsing with express.json(), and how Express bridges client-side fetch() requests to database persistence.
Before writing backend code, it is critical to understand that Node.js and Express are not the same thing:
Node.js is an open-source, cross-platform JavaScript runtime environment powered by Google's V8 engine and libuv. Historically, JavaScript only executed inside web browsers. Node.js enables JavaScript to run directly on server operating systems (Linux, macOS, Windows) with access to the filesystem, network sockets, and system processes.
Express is a minimal, fast, and unopinionated web framework built on top of Node.js. While Node.js provides raw, low-level HTTP primitives (`http.createServer`), Express provides clean abstractions for URL routing, middleware composition, HTTP method handling, and JSON response helpers.
Node.js ≠ Express Node.js --> The ENGINE & RUNTIME (Executes JS, manages threads, network I/O, event loop) Express --> The FRAMEWORK & STEERING (Routes URLs, parses bodies, orchestrates middleware)
Building an Express 5 backend requires three straightforward steps: initializing the project, installing dependencies, and writing the server entry point.
Initialize a new package.json and enable modern ES Modules:
npm init -y npm pkg set type="module"
Install the latest Express release from npm:
npm install express@5
Execute with Node.js built-in watch mode:
node --watch server.js
import express from 'express';
const app = express();
const PORT = 3000;
// Basic GET route returning JSON
app.get('/api/hello', (req, res) => {
res.status(200).json({
message: 'Hello from the server!'
});
});
// Bind server to network port
app.listen(PORT, () => {
console.log(`Server listening on http://localhost:${PORT}`);
});In Express, a Route connects an incoming HTTP request (defined by its Method and URL path) to a backend handler function. The handler receives two primary objects: req (the incoming Request) and res (the outgoing Response).
Dynamic segments in the URL path, prefixed by a colon ::
// URL: /api/tasks/42
app.get('/api/tasks/:id', (req, res) => {
const { id } = req.params; // "42"
});Key-value pairs appended after the question mark ?:
// URL: /api/tasks?status=done&limit=10
app.get('/api/tasks', (req, res) => {
const { status, limit } = req.query;
});JSON payload sent in the HTTP request body (parsed by middleware):
// POST { "title": "Buy milk" }
app.post('/api/tasks', (req, res) => {
const { title } = req.body;
});In Express, Middleware is code that sits in the pipeline between when a client request arrives and when the final route handler sends a response.
When a browser makes a POST or PATCH request, the HTTP payload arrives across the network as a raw stream of binary bytes. Node.js does not parse these bytes by default. Calling app.use(express.json()) mounts built-in middleware that listens to incoming stream chunks, buffers them, verifies the Content-Type: application/json header, and parses the JSON string into a JavaScript object attached to req.body.
// Custom middleware has three arguments: (req, res, next)
app.use((req, res, next) => {
console.log(`[${new Date().toISOString()}] ${req.method} ${req.url}`);
// ALWAYS call next() to pass control to the subsequent middleware or route!
next();
});Operate a live Node.js + Express 5 backend server. Edit the server code on the left, dispatch HTTP requests from the API client panel on the right, and inspect live server terminal logs and response bodies below:
[
{
"id": 1,
"title": "Install Node.js LTS and Express 5",
"completed": true
},
{
"id": 2,
"title": "Understand req.params and req.body",
"completed": false
}
]In a production full-stack application, Express functions as the central nervous system connecting the frontend user interface to backend business logic and database persistence:
fetch('/api/tasks', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ title: 'Finish assignment' }) }) is dispatched over HTTP.app.use(express.json()) reads the raw byte stream chunks and constructs req.body = { title: 'Finish assignment' }.app.post('/api/tasks') handler validates the input and issues a SQL query INSERT INTO tasks (title) VALUES ($1) RETURNING * to PostgreSQL.res.status(201).json(createdTask). The frontend parses the response and updates its React state to render the new item.Examine real-world errors and broken server implementations encountered by backend developers:
const app = express();
// Forgot: app.use(express.json());
app.post('/api/tasks', (req, res) => {
const { title } = req.body;
console.log(title);
res.status(201).json({ id: 1, title });
});Why is req.body undefined when the client sends a valid JSON POST request?
// Route 1 (Parameterized):
app.get('/api/tasks/:id', (req, res) => {
res.json({ taskId: req.params.id });
});
// Route 2 (Specific literal path):
app.get('/api/tasks/completed', (req, res) => {
res.json({ filter: 'all completed tasks' });
});Why did Express match the route parameter :id instead of the /completed endpoint?
app.post('/api/tasks', (req, res) => {
const { title } = req.body;
tasks.push({ id: 99, title });
console.log('Task saved to array!');
// Forgot res.status(201).json(...) or res.send(...)
});Why did the client browser freeze and eventually timeout?
const PORT = 3000;
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});What causes the EADDRINUSE error when launching a Node.js server?
Build a complete 5-endpoint CRUD Notes API (/api/notes) using Express 5. Complete each architectural implementation step:
You are setting up the entry file server.js for the Notes API. You need to import Express, instantiate the application, and register the body parser middleware.
import express from 'express'; const app = express(); const PORT = process.env.PORT || 5000; // Which line of code correctly mounts JSON body parsing in Express 5? ???