Menu
CoddyTech

Your First Middleware

Part of the Express Basics section of Coddy's Node.js journey. Lesson 13 of 23.

Some work belongs to every request, like writing a line to the log, and copying it into each route would never end. Every request has a method, in req.method: GET to fetch something, POST to send something.

let visits = 0;

app.use((req, res, next) => {
    visits++;
    next();
});

A function given to app.use is middleware: Express runs it for every request, in the order it was added, before the routes below it. It gets a third argument, next. Calling next() hands the request on to whatever comes next. A middleware that neither answers nor calls next() leaves the request waiting forever.

express.static from the last chapter is middleware too: it answers when it finds a file and calls next() when it does not.

challenge icon

Challenge

Easy

You want a log line for every request your server gets, so you can see what visitors ask for.

  1. In server.js, right after const app = express();, add a middleware with app.use. Placed there, it runs before every route.
  2. For each request, it prints one line to the Console: the request's method, a space, then its path. A request for /recipes/2 prints GET /recipes/2. The method is in req.method (GET, POST and so on) and the path is in req.url.
  3. After printing, it calls next(), so the request goes on to the routes.

Try it: Press Run and send /recipes/2. You should see Pancakes, and the Console shows GET /recipes/2. Send /api/recipes: the list as usual, and GET /api/recipes in the Console. Send /nope: a 404, and the Console still shows GET /nope, because the middleware runs before Express looks for a route.

Try it yourself

import { escapeHtml } from './html.js';
import express from 'express';
import { recipes } from './data.js';

const app = express();

app.use(express.static('public'));

app.get('/', (req, res) => {
    const items = recipes.map((r) => `<li><a href="/recipes/${r.id}">${escapeHtml(r.title)}</a></li>`);
    res.send(`<link rel="stylesheet" href="/style.css"><h1>Recipe Box</h1><ul>${items.join('')}</ul>`);
});

// type('text') sends plain text, so each title shows on its own line.
app.get('/recipes', (req, res) => {
    const { tag } = req.query;
    const list = tag ? recipes.filter((recipe) => recipe.tags.includes(tag)) : recipes;
    res.type('text').send(list.map((recipe) => recipe.title).join('\n'));
});

app.get('/recipes/:id', (req, res) => {
    const recipe = recipes.find((r) => r.id === Number(req.params.id));
    if (!recipe) {
        res.status(404).send('No such recipe');
        return;
    }
    res.send(recipe.title);
});

app.get('/api/recipes', (req, res) => {
    res.json(recipes);
});

app.get('/api/recipes/:id', (req, res) => {
    const recipe = recipes.find((r) => r.id === Number(req.params.id));
    if (!recipe) {
        res.status(404).json({ error: 'No such recipe' });
        return;
    }
    res.json(recipe);
});

app.get('/about', (req, res) => {
    res.send('Recipe Box collects family recipes.');
});

app.get('/menu', (req, res) => {
    res.redirect(301, '/recipes');
});

app.get('/menu/:id', (req, res) => {
    res.redirect(301, `/recipes/${req.params.id}`);
});

app.listen(3000, () => {
    console.log('Recipe Box is running on port 3000');
});
quiz iconTest yourself

This lesson includes a short quiz. Start the lesson to answer it and track your progress.

All lessons in Express Basics

Practice on your own: Express playground