03. Express Basics

Express is the most popular Node.js web framework. It provides routing, middleware, and utilities that make web development much easier than using raw http.

Demo Files

Setup: npm install then node hello-express.js

Try it live: cse135.site:3002/demo - An Express server running on this site

Why Express?

Compare Module 02's routing code to Express:

Raw Node.js (http module) Express
if (req.url === '/about' &&
    req.method === 'GET') {
    res.writeHead(200, {...});
    res.end('About page');
}
app.get('/about', (req, res) => {
    res.send('About page');
});

Express handles URL parsing, headers, and much more automatically.

Getting Started with npm

Express is installed via npm (Node Package Manager):

# Create a new project
mkdir my-app
cd my-app

# Initialize package.json
npm init -y

# Install Express
npm install express

This creates:

Hello Express

// hello-express.js
const express = require('express');
const app = express();

app.get('/', (req, res) => {
    res.send('Hello World!');
});

app.listen(3000, () => {
    console.log('Server running on http://localhost:3000');
});

That's it! No manual URL parsing, no header management, no content-type setting.

Routing

Express provides methods for each HTTP verb:

// GET request
app.get('/users', (req, res) => {
    res.send('Get all users');
});

// POST request
app.post('/users', (req, res) => {
    res.send('Create a user');
});

// PUT request
app.put('/users/:id', (req, res) => {
    res.send(`Update user ${req.params.id}`);
});

// DELETE request
app.delete('/users/:id', (req, res) => {
    res.send(`Delete user ${req.params.id}`);
});

Route Parameters

// :id is a parameter
app.get('/users/:id', (req, res) => {
    const userId = req.params.id;  // Access the parameter
    res.send(`User ID: ${userId}`);
});

// Multiple parameters
app.get('/posts/:year/:month', (req, res) => {
    const { year, month } = req.params;
    res.send(`Posts from ${month}/${year}`);
});

Query Strings

// URL: /search?q=nodejs&page=2
app.get('/search', (req, res) => {
    const query = req.query.q;     // 'nodejs'
    const page = req.query.page;   // '2'
    res.send(`Searching for: ${query}, page ${page}`);
});

Reading Request Data

Express provides convenient ways to access all parts of a request:

Headers

// Express provides req.get() as a shortcut
app.get('/info', (req, res) => {
    const userAgent = req.get('user-agent');   // Cleaner than req.headers['user-agent']
    const contentType = req.get('content-type');
    const host = req.get('host');

    // Client IP (handles proxies)
    const clientIP = req.ip;  // or req.headers['x-forwarded-for']

    res.json({ userAgent, contentType, host, clientIP });
});

Request Body

To access POST data in req.body, you must enable the parsing middleware first:

// IMPORTANT: Add these BEFORE your routes!
app.use(express.json());                         // For JSON bodies
app.use(express.urlencoded({ extended: true })); // For form bodies

app.post('/login', (req, res) => {
    // Now req.body contains the parsed data
    const { username, password } = req.body;
    res.json({ received: { username, password } });
});
Common mistake: If req.body is undefined, you forgot to add the body-parsing middleware, or you added it AFTER your routes. Middleware order matters!

Comparison: Raw Node.js vs Express

Task Raw Node.js Express
Query string url.parse(req.url, true).query req.query
Route parameters Manual URL parsing req.params
Headers req.headers['user-agent'] req.get('user-agent')
POST body Collect stream chunks manually req.body (with middleware)
Client IP req.connection.remoteAddress req.ip

Response Methods

Express provides convenient response methods:

// Send plain text or HTML
res.send('Hello');
res.send('<h1>Hello</h1>');

// Send JSON (sets Content-Type automatically)
res.json({ name: 'Alice', age: 25 });

// Send a file
res.sendFile('/path/to/file.html');

// Set status code
res.status(404).send('Not found');

// Redirect
res.redirect('/other-page');
res.redirect(301, '/permanent-redirect');

Serving Static Files

Remember the complex static file code from Module 02? Express does it in one line:

// Serve files from the 'public' directory
app.use(express.static('public'));

Now files in public/ are accessible:

Middleware

Middleware functions run before your route handlers. They can:

// Middleware runs for ALL requests
app.use((req, res, next) => {
    console.log(`${req.method} ${req.url}`);
    next();  // Continue to the next handler
});

// Built-in middleware for parsing JSON
app.use(express.json());

// Built-in middleware for parsing form data
app.use(express.urlencoded({ extended: true }));
The next() function: Middleware must call next() to pass control to the next handler, or send a response. If you forget, the request will hang.

Middleware and Route Ordering

In Express, order matters. Middleware and routes are processed in the order they're defined. This is a common source of bugs.

Middleware Must Come Before Routes

// WRONG - body parser comes after route
app.post('/data', (req, res) => {
    console.log(req.body);  // undefined!
});
app.use(express.json());    // Too late!

// CORRECT - body parser comes first
app.use(express.json());    // Parse JSON bodies
app.post('/data', (req, res) => {
    console.log(req.body);  // Works!
});

Route Order Matters

// WRONG - catch-all route comes first
app.get('/users/*', (req, res) => {
    res.send('Catch all');
});
app.get('/users/profile', (req, res) => {
    res.send('Profile page');  // Never reached!
});

// CORRECT - specific routes first
app.get('/users/profile', (req, res) => {
    res.send('Profile page');  // Matches first
});
app.get('/users/*', (req, res) => {
    res.send('Catch all');     // Only if above didn't match
});

404 Handler Must Be Last

// WRONG - 404 handler catches everything
app.use((req, res) => {
    res.status(404).send('Not found');
});
app.get('/about', (req, res) => {
    res.send('About');  // Never reached!
});

// CORRECT - 404 at the very end
app.get('/about', (req, res) => {
    res.send('About');
});
// ... all other routes ...
app.use((req, res) => {
    res.status(404).send('Not found');  // Only unmatched requests reach here
});
Debugging tip: If a route isn't working, check if something defined earlier is catching the request. Add logging middleware at the top to see what's happening: app.use((req, res, next) => { console.log(req.method, req.url); next(); });

Complete Example

// routes.js - Complete Express example
const express = require('express');
const app = express();

// Middleware
app.use(express.json());
app.use(express.static('public'));

// Routes
app.get('/', (req, res) => {
    res.send('<h1>Welcome</h1><a href="/api/items">View Items</a>');
});

// API routes
app.get('/api/items', (req, res) => {
    res.json([
        { id: 1, name: 'Item 1' },
        { id: 2, name: 'Item 2' }
    ]);
});

app.get('/api/items/:id', (req, res) => {
    res.json({ id: req.params.id, name: `Item ${req.params.id}` });
});

app.post('/api/items', (req, res) => {
    const newItem = req.body;
    res.status(201).json({ message: 'Created', item: newItem });
});

// 404 handler (must be last)
app.use((req, res) => {
    res.status(404).json({ error: 'Not found' });
});

app.listen(3000);

Try It Live

We have an Express server running on this site. Try the interactive demo to see headers, query strings, route parameters, and request bodies in action:

Live Demo: cse135.site:3002/demo

This demo lets you:

View the source: express-demo.js

Summary