Skip to content

ExpressJS - Serving Dynamic Content

Express.js - Serving Dynamic Content with Template Engines

Section titled “Express.js - Serving Dynamic Content with Template Engines”

Express applications often need to generate dynamic HTML content based on data from a database or user input. Instead of manually constructing HTML strings in your JavaScript code (which is error-prone and hard to maintain), you should use a template engine.

A template engine allows you to write static template files (like HTML, but with special syntax) and inject dynamic data into them at runtime. This practice separates presentation (HTML) from logic (JavaScript), leading to cleaner and more organized code. Popular template engines for Express include Pug (formerly Jade), EJS, and Nunjucks.

In this tutorial, we’ll use Pug for its clean, indentation-based syntax. The principles, however, apply to any template engine.

Let’s build a simple app that takes a number from a form, calculates its square on the server, and displays the result.

You only need express and pug for this example.

npm install express pug

First, you need to tell Express which template engine to use and where to find the template files (called ‘views’).

index.js
import express from 'express';
import path from 'path';
import { fileURLToPath } from 'url';
const app = express();
const PORT = 3000;
// Necessary for __dirname in ES Modules
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
// 1. Set the view engine to Pug
app.set('view engine', 'pug');
// 2. Specify the directory where the template files are located
app.set('views', path.join(__dirname, 'views'));

To process data from an HTML form, you need to use the express.urlencoded() middleware. This middleware parses incoming requests with x-www-form-urlencoded payloads (the default for HTML forms) and populates the req.body object.

Note: In older versions of Express, this required the external body-parser package. It is now built into Express.

// 3. Use middleware to parse form data
app.use(express.urlencoded({ extended: true }));

Create a directory named views and inside it, a file named calculator.pug.

views/calculator.pug
html
head
title Dynamic Content Calculator
style.
body { font-family: sans-serif; padding: 20px; }
input, button { font-size: 1rem; padding: 5px; }
body
h1 Square Calculator
form(action="/calculate", method="POST")
div
label(for="number") Enter a Number:
input#number(name="number" type="number" value=inputValue || '')
br
button(type="submit") Calculate Square
//- This block only renders if 'result' is passed from the server
if result
h2 Result: #{result}

Now, let’s complete our index.js file with the routes to render the form and handle the calculation.

// index.js (continued)
// Route to display the form
app.get('/', (req, res) => {
// res.render() finds 'calculator.pug' and sends it as HTML
res.render('calculator');
});
// Route to handle the form submission
app.post('/calculate', (req, res) => {
const number = parseFloat(req.body.number);
// Basic validation
if (isNaN(number)) {
return res.render('calculator', {
inputValue: req.body.number,
result: 'Error: Please enter a valid number.'
});
}
const square = number * number;
// Render the same template, but pass data to it
res.render('calculator', {
inputValue: number,
result: `The square of ${number} is ${square}.`
});
});
app.listen(PORT, () => {
console.log(`Server is running on http://localhost:${PORT}`);
});
  1. Run your server with node index.js.
  2. Open your browser to http://localhost:3000.
  3. Enter a number and click the ‘Calculate Square’ button.
  4. The page will reload, displaying the calculated result right below the form.
  • req.body is undefined: This almost always means you forgot to include the app.use(express.urlencoded({ extended: true })); middleware, or you placed it after your route handler.
  • Failed to lookup view ”…”: This error means Express cannot find your template file. Double-check that app.set('views', ...) points to the correct directory and that the filename in res.render() is correct.