Set up your first Express application and understand why Express is the most popular Node.js framework.
In the previous lesson, you built an HTTP server with Node's built-in http module. You manually checked URLs with if/else, set headers by hand, and stringified JSON yourself. It worked, but it was verbose and tedious. Now imagine building a real application with dozens of routes, authentication, input validation, error handling, and logging. The raw http module would become a nightmare.
Express is a minimal, unopinionated web framework for Node.js that solves exactly these problems. It is the most popular Node.js framework by a massive margin — with over 60,000 stars on GitHub and billions of downloads. When people say "Node.js backend," they usually mean "Express backend."
Here is what Express gives you over the raw http module:
app.get('/users', handler) and app.post('/users', handler). Each route is a single, readable line.app.use(express.json()). Need to log every request? Add a logging middleware. Need to check authentication? Add an auth middleware. Middleware is Express's killer feature./users/:id), query strings (?page=2), and (with middleware) JSON request bodies. No manual string splitting.res.writeHead(200, {'Content-Type':'application/json'}); res.end(JSON.stringify(data)), you just write res.json(data). Express handles the headers and serialization.Express is unopinionated, meaning it does not force you into a specific project structure or pattern. You decide how to organize your files, what database to use, and what libraries to add. This flexibility is why Express is used for everything from tiny microservices to large-scale production APIs serving millions of users.
Setting up an Express application is straightforward. First, you need a Node.js project with a package.json (which you learned about in the npm lesson). Then you install Express as a dependency.
Step 1: Initialize your project
mkdir my-api
cd my-api
npm init -yStep 2: Install Express
npm install express
This downloads Express into node_modules and adds it to your dependencies in package.json.
Step 3: Create your server file
Create a file called server.js (or app.js — naming is up to you). This is where your Express application lives.
The basic Express application follows a simple pattern:
require('express')express()app.use() — like JSON parsingapp.get(), app.post(), etc.app.listen(port)Compare this to the raw http module:
http module (verbose):
const http = require('http');
const server = http.createServer((req, res) => {
if (req.method === 'GET' && req.url === '/') {
res.writeHead(200, {'Content-Type': 'application/json'});
res.end(JSON.stringify({ message: 'Hello' }));
}
});
server.listen(3000);Express (clean):
const express = require('express');
const app = express();
app.get('/', (req, res) => res.json({ message: 'Hello' }));
app.listen(3000);The Express version is shorter, more readable, and much easier to extend. As your application grows, the difference becomes even more dramatic.
// Import the Express framework
const express = require('express');
// Create an Express application instance
const app = express();
// Middleware: parse JSON bodies on incoming requests
// Without this, req.body would be undefined for POST/PUT requests
app.use(express.json());
// Define a GET route for the root URL
app.get('/', (req, res) => {
// res.json() automatically:
// 1. Sets Content-Type to application/json
// 2. Converts the object to a JSON string
// 3. Sends the response
res.json({ message: 'Hello World' });
});
// Define a GET route for /api/status
app.get('/api/status', (req, res) => {
res.json({
status: 'running',
timestamp: new Date().toISOString(),
uptime: process.uptime()
});
});
// Start the server on port 3000
const PORT = 3000;
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});Let's do a clear side-by-side comparison of the same functionality — a server with three routes — built with the raw http module versus Express. This will crystallize why Express is the standard choice.
Returning JSON (http module):
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ name: 'Alice' }));Returning JSON (Express):
res.json({ name: 'Alice' });Express reduces three operations (set header, stringify, send) into one method call.
Routing (http module):
if (req.method === 'GET' && req.url === '/users') { ... }
else if (req.method === 'POST' && req.url === '/users') { ... }
else if (req.method === 'GET' && req.url === '/products') { ... }Routing (Express):
app.get('/users', getUsers);
app.post('/users', createUser);
app.get('/products', getProducts);Each route is a clean, declarative line instead of nested conditionals.
URL parameters (http module):
// For /users/123, you manually parse:
const parts = req.url.split('/');
const userId = parts[2]; // "123"URL parameters (Express):
app.get('/users/:id', (req, res) => {
const userId = req.params.id; // "123"
});Express automatically extracts named parameters from the URL.
The key insight is that Express is not magic — it is an abstraction layer built on top of the http module. Under the hood, Express still creates an http server, still receives request and response objects, and still calls res.end(). It just wraps all of that behind a cleaner, more productive API. When you call res.json(), Express is calling res.writeHead() and res.end(JSON.stringify()) for you. Understanding the http module helps you appreciate what Express does and debug issues at a lower level.
When you develop a Node.js application, you make changes to your code and need to see the results. The problem is that Node.js loads your file once and keeps running the old version. Every time you change your code, you have to:
node server.js againDoing this hundreds of times a day is painful. nodemon (node monitor) solves this by watching your files and automatically restarting the server whenever you save a change.
Installation:
npm install --save-dev nodemon
Notice we use --save-dev because nodemon is a development tool — you do not need it in production where the code does not change.
Add it to your npm scripts:
"scripts": {
"start": "node server.js",
"dev": "nodemon server.js"
}Now run npm run dev and nodemon starts your server. Edit any .js file and save — nodemon detects the change and restarts automatically. You will see something like:
[nodemon] restarting due to changes...
[nodemon] starting `node server.js`
Server running on port 3000Important distinction: Use npm run dev (with nodemon) during development for the auto-restart feature. Use npm start (plain node) in production where your code is stable and you do not want unnecessary restarts.
Configuration: nodemon watches .js, .mjs, .json files by default. You can customize it with a nodemon.json file or command-line flags:
{
"watch": ["src"],
"ext": "js,json",
"ignore": ["tests/*"]
}nodemon is one of those tools that every Node.js developer installs in every project. It is a small quality-of-life improvement that saves significant time over the course of a project.
What does express.json() do?