Beginner20 min read

Introduction to Express

Set up your first Express application and understand why Express is the most popular Node.js framework.

Why Express?

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:

  • Routing — Instead of long if/else chains, you get clean methods like app.get('/users', handler) and app.post('/users', handler). Each route is a single, readable line.
  • Middleware — Plug-in functions that run before your route handlers. Need to parse JSON bodies? One 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.
  • Request parsing — Express automatically parses URL parameters (/users/:id), query strings (?page=2), and (with middleware) JSON request bodies. No manual string splitting.
  • Response helpers — Instead of res.writeHead(200, {'Content-Type':'application/json'}); res.end(JSON.stringify(data)), you just write res.json(data). Express handles the headers and serialization.
  • Error handling — Express has a built-in error handling mechanism that catches errors and sends appropriate responses.

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 Express

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

html
mkdir my-api
cd my-api
npm init -y

Step 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:

  1. Import Express with require('express')
  2. Create an app instance with express()
  3. Add middleware with app.use() — like JSON parsing
  4. Define routes with app.get(), app.post(), etc.
  5. Start listening with app.listen(port)

Compare this to the raw http module:

http module (verbose):

javascript
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):

javascript
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.

Complete Express Starter

javascript
// 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}`);
});

Express vs http Module

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):

javascript
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ name: 'Alice' }));

Returning JSON (Express):

javascript
res.json({ name: 'Alice' });

Express reduces three operations (set header, stringify, send) into one method call.

Routing (http module):

javascript
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):

javascript
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):

javascript
// For /users/123, you manually parse:
const parts = req.url.split('/');
const userId = parts[2]; // "123"

URL parameters (Express):

javascript
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.

nodemon for Development

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:

  1. Go to the terminal
  2. Press Ctrl+C to stop the server
  3. Run node server.js again
  4. Go back to the browser and test

Doing 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:

json
"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:

html
[nodemon] restarting due to changes...
[nodemon] starting `node server.js`
Server running on port 3000

Important 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:

json
{
  "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?

Ready to practice?

Create your free account to access the interactive code editor, run challenges, and track your progress.