Use Template Engines in Express
18 min read·Jan 1, 2025
In Express, a template engine is a module that facilitates the dynamic generation of HTML pages at runtime by combining static HTML markup with dynamic data.
In this lesson, we'll use the Mustache template engine available under the npm package name mustache-express:
$ npm i --save express mustache-express
Set up the templates directory
In Express, template files are by convention placed in a directory with an explicit name, such as views or templates, located in the root directory of the project.
To tell Express where to find these templates, you can use the set() method of the application instance, which is used to modify the application settings:
app.set('views', path);
Where:
'views'is the name of the application setting to modify.pathis the relative path to the directory containing the views.
Example
Let's consider this project's directory structure:
project
├── app.js
└── templates
├── index
├── ...
└── ...
In this example, we're telling Express that the template directory is located in the templates directory of the current directory:
const path = require('node:path');
const express = require('express');
const server = express();
server.set('views', path.join(__dirname, 'templates'));
Set up the default template engine
To register a template engine for rendering views in Express, you can use the engine() method of the application instance:
server.engine(extension, engine);
Where:
extensionis a string that specifies the file extension associated with the template engine, for example'pug'for Pug,'mst'for Mustache, etc.engineis a function responsible for rendering the template.
Once the template engine is registered, you can tell Express to use it as the default engine for rendering the files with the specified file extension, using the set() method of the application instance:
server.set('view engine', extension);
Example
In this example, we're setting Mustache as the default template engines for templates with an .mst file extension:
const path = require('node:path');
const express = require('express');
const mustache = require('mustache-express');
const server = express();
server.set('views', path.join(__dirname, 'templates'));
server.engine('mst', mustache());
server.set('view engine', 'mst');
Render template files
Most template engine provide a render() method accessible through the response object provided by the request listener function of the route:
app.METHOD(PATH, (req, res) => {
res.render(template, data);
});
Where:
templateis the name of the template file to render.datais an optional object containing a list of variables to inject in the template before rendering.
To inject variables into a Mustache template, you can use the double curly brackets syntax:
{{variable}}
Example
Let's consider this project's directory structure:
project
├── app.js
├── recipes
│ └── pancakes.json
└── templates
├── index.mst
├── not_found.mst
└── recipe.mst
Where, the templates/index.mst file contains a static HTML template of a home page:
<html>
<head>
<title>Recipes</title>
</head>
<body style="text-align: center">
<h1>Welcome to Recipes!</h1>
<p>Send HTTP GET requests to `/recipe/:name` with cURL to get a recipe.</p>
</body>
</html>
Where, the templates/recipe.mst file contains an HTML template of a recipe:
<html>
<head>
<title>{{name}}</title>
</head>
<body>
<h1>{{name}}</h1>
<h2>Ingredients</h2>
<ul>
{{#ingredients}}
<li>{{.}}</li>
{{/ingredients}}
</ul>
<h2>Steps</h2>
<ul>
{{#steps}}
<li>{{.}}</li>
{{/steps}}
</ul>
</body>
</html>
Where, the templates/not_found.mst file contains an HTML template of a 404 page not found:
<html>
<head>
<title>Not Found</title>
</head>
<body style="text-align: center">
<h1>Page not found...</h1>
<a href="/">Back to homepage</a>
</body>
</html>
Where, the recipes/pancakes.json file contains a JSON object describing a recipe:
{
"name": "Pancakes",
"ingredients": [
"1 cup all-purpose flour",
"2 tbsp sugar",
"1 tsp baking powder",
"1/2 tsp baking soda",
"1/4 tsp salt",
"1 cup buttermilk (or milk with 1 tbsp vinegar added)",
"1 large egg",
"2 tbsp melted butter (plus extra for cooking)",
"1 tsp vanilla extract (optional)"
],
"steps": [
"In a large bowl, whisk together the flour, sugar, baking powder, baking soda, and salt.",
"In another bowl, combine the buttermilk, egg, melted butter, and vanilla extract.",
"Pour the wet ingredients into the dry ingredients and stir until just combined. The batter may be a bit lumpy—do not overmix.",
"Heat a non-stick skillet or griddle over medium heat and add a little melted butter to coat the surface.",
"Pour about 1/4 cup of batter onto the skillet for each pancake. Cook until bubbles form on the surface and the edges look set (about 2-3 minutes).",
"Flip the pancakes and cook for another 1-2 minutes until golden brown.",
"Serve warm with maple syrup, fresh fruit, or your favorite toppings."
]
}
Where, the app.js file contains a server that dynamically renders and serves Mustache template files based on a route parameter:
const path = require('node:path');
const fs = require('node:fs');
const express = require('express');
const mustache = require('mustache-express');
// Initialize a new Express instance
const server = express();
// Set the `templates` directory as the views directory
server.set('views', path.join(__dirname, 'templates'));
// Map the `.mst` file extension to the Mustache engine
server.engine('mst', mustache());
// Set the Mustache engine as the default template engine
server.set('view engine', 'mst');
// Catch incoming `HTTP GET /` requests
server.get('/', (req, res) => {
// Render and send the `index.mst` template.
res.render('index');
});
// Catch incoming `HTTP GET /recipe/:name` requests
server.get('/recipe/:name', (req, res, next) => {
const { name } = req.params;
// Forward the request to the next route if the `name` parameter is undefined
if (!name) {
return next();
}
try {
// Read the content of the file located at `recipes/[name].json`
const recipe = fs.readFileSync(path.join(__dirname, 'recipes', name + '.json'));
// Convert the file's content into an object literal
const data = JSON.parse(recipe);
// Render and send the `recipe.mst` template using the parsed data
res.render('recipe', data);
} catch(error) {
// Forward the request to the next route if there is any error
return next();
}
});
// Catch all requests
server.all('/*splat', (req, res) => {
// Render and send the `not_found.mst` template.
res.render('not_found');
});
// Listen on the development port 3000
server.listen(3000);
Summary
Here's a summary of what you've learned in this lesson:
- The
app.set('views')method is used to specify the path to the templates. - The
app.set('view engine')method is used to specify the default template engine. - The
app.engine()method is used to map a file extension to a template engine. - The
res.render()method is used to render a template and send the rendered HTML string to the client.
Enjoying the courses?
I've made these courses completely free so anyone can learn from them. If they've helped you and you'd like to actively support the work behind BackendBrewery, you can leave a tip:
Support BackendBrewery