Express Template Rendering
Introduction
Template rendering is a crucial aspect of web development that allows you to generate dynamic HTML content on the server side before sending it to the client. Express.js, as a popular Node.js framework, provides excellent support for various templating engines that simplify the process of creating dynamic web pages.
In this guide, we'll explore how Express handles template rendering, set up different templating engines, and learn best practices for organizing and rendering views in your Express applications.
What is Template Rendering?
Template rendering is the process of combining data with templates to generate dynamic HTML content. Instead of writing static HTML files or concatenating strings in your JavaScript code, templating engines allow you to:
- Create template files with placeholders for dynamic data
- Pass data from your Express routes to these templates
- Generate complete HTML documents that are sent to the client
This approach separates your application logic from the presentation layer, making your code more maintainable and easier to understand.
Setting Up Template Rendering in Express
Before you can render templates, you need to configure Express to use a templating engine. Let's walk through the basic setup:
Step 1: Install a Templating Engine
Express supports many templating engines, with some of the most popular being:
- EJS (Embedded JavaScript)
- Pug (formerly Jade)
- Handlebars (via express-handlebars)
Let's start with EJS as an example:
npm install ejs
Step 2: Configure Express to Use the Templating Engine
In your main Express application file (typically app.js
or server.js
):
const express = require('express');
const path = require('path');
const app = express();
// Set the view engine
app.set('view engine', 'ejs');
// Set the directory where templates are located
app.set('views', path.join(__dirname, 'views'));
Step 3: Create a Template
Create a directory called views
in your project root, and add a template file. For example, views/index.ejs
:
<!DOCTYPE html>
<html>
<head>
<title><%= title %></title>
</head>
<body>
<h1><%= title %></h1>
<p>Welcome to <%= title %></p>
</body>
</html>
Step 4: Render the Template from a Route
Now you can render this template from your Express routes:
app.get('/', (req, res) => {
// Render the 'index' template with data
res.render('index', { title: 'Express Template Rendering' });
});
When a user visits the root URL, Express will:
- Load the
index.ejs
template - Replace
<%= title %>
with "Express Template Rendering" - Send the resulting HTML to the client
Working with Different Templating Engines
Let's explore how to work with different templating engines in Express:
EJS (Embedded JavaScript)
EJS uses <%= %>
tags to insert values and <% %>
for JavaScript logic:
<!-- views/users.ejs -->
<!DOCTYPE html>
<html>
<head>
<title>User List</title>
</head>
<body>
<h1>User List</h1>
<ul>
<% users.forEach(function(user) { %>
<li><%= user.name %> - <%= user.email %></li>
<% }); %>
</ul>
</body>
</html>
Rendering this template:
app.get('/users', (req, res) => {
const userData = [
{ name: 'John', email: '[email protected]' },
{ name: 'Jane', email: '[email protected]' },
{ name: 'Bob', email: '[email protected]' }
];
res.render('users', { users: userData });
});
Pug (formerly Jade)
First, install Pug:
npm install pug
Configure Express:
app.set('view engine', 'pug');
app.set('views', path.join(__dirname, 'views'));
Create a template (views/profile.pug
):
doctype html
html
head
title User Profile
body
h1 User Profile
if user
h2 #{user.name}
p Email: #{user.email}
if user.bio
p Bio: #{user.bio}
else
p No bio provided
else
p User not found
Render this template:
app.get('/profile/:id', (req, res) => {
// In a real app, you would fetch the user from a database
const user = {
name: 'John Doe',
email: '[email protected]',
bio: 'Web developer and coffee enthusiast'
};
res.render('profile', { user });
});
Handlebars (via express-handlebars)
First, install express-handlebars:
npm install express-handlebars
Configure Express:
const exphbs = require('express-handlebars');
// Setup Handlebars
app.engine('handlebars', exphbs.engine({
defaultLayout: 'main'
}));
app.set('view engine', 'handlebars');
app.set('views', path.join(__dirname, 'views'));
Create a layout (views/layouts/main.handlebars
):
Create a template (views/about.handlebars
):
Render this template:
app.get('/about', (req, res) => {
res.render('about', {
title: 'About Us',
content: 'We are a team of dedicated developers.',
team: [
{ name: 'Alice', role: 'Developer' },
{ name: 'Bob', role: 'Designer' },
{ name: 'Charlie', role: 'Manager' }
]
});
});
Advanced Template Rendering Concepts
Partials and Includes
Most templating engines support partials or includes, which allow you to reuse template fragments across multiple pages.
EJS Includes
<!-- views/partials/header.ejs -->
<header>
<h1>My Website</h1>
<nav>
<a href="/">Home</a>
<a href="/about">About</a>
</nav>
</header>
Using the partial:
<!-- views/index.ejs -->
<!DOCTYPE html>
<html>
<head>
<title><%= title %></title>
</head>
<body>
<%- include('partials/header') %>
<main>
<h2><%= title %></h2>
<p><%= content %></p>
</main>
</body>
</html>
Handlebars Partials
Register partials:
const hbs = exphbs.create({
defaultLayout: 'main',
partialsDir: path.join(__dirname, 'views/partials')
});
app.engine('handlebars', hbs.engine);
Create a partial (views/partials/sidebar.handlebars
):
Using the partial:
Passing Functions to Templates
You can pass functions to templates for more complex logic:
app.get('/blog', (req, res) => {
res.render('blog', {
posts: getBlogPosts(),
formatDate: (date) => {
return new Date(date).toLocaleDateString('en-US', {
year: 'numeric',
month: 'long',
day: 'numeric'
});
}
});
});
In your EJS template:
<div class="post">
<h3><%= post.title %></h3>
<p class="date">Published: <%= formatDate(post.publishedAt) %></p>
<div class="content">
<%= post.content %>
</div>
</div>
Error Handling in Templates
Always implement error handling when rendering templates:
app.get('/product/:id', (req, res, next) => {
try {
const product = getProductById(req.params.id);
if (!product) {
return res.status(404).render('error', {
title: 'Not Found',
message: 'Product not found'
});
}
res.render('product', { product });
} catch (error) {
next(error);
}
});
Real-world Example: Blog Application
Let's build a simple blog application to demonstrate template rendering in a practical scenario:
Project Structure
blog-app/
├── node_modules/
├── views/
│ ├── layouts/
│ │ └── main.handlebars
│ ├── partials/
│ │ ├── header.handlebars
│ │ ├── footer.handlebars
│ │ └── post-card.handlebars
│ ├── home.handlebars
│ ├── post.handlebars
│ └── error.handlebars
├── public/
│ ├── css/
│ │ └── style.css
│ └── js/
│ └── main.js
├── data/
│ └── posts.js
├── app.js
└── package.json
Setting Up the Application
// app.js
const express = require('express');
const exphbs = require('express-handlebars');
const path = require('path');
const { getPosts, getPostById } = require('./data/posts');
const app = express();
// Setup handlebars
const hbs = exphbs.create({
defaultLayout: 'main',
extname: '.handlebars',
helpers: {
formatDate: (date) => {
return new Date(date).toLocaleDateString('en-US', {
year: 'numeric', month: 'long', day: 'numeric'
});
}
}
});
app.engine('handlebars', hbs.engine);
app.set('view engine', 'handlebars');
app.set('views', path.join(__dirname, 'views'));
// Serve static files
app.use(express.static(path.join(__dirname, 'public')));
// Routes
app.get('/', (req, res) => {
const posts = getPosts();
res.render('home', {
title: 'My Blog',
posts
});
});
app.get('/post/:id', (req, res) => {
const post = getPostById(req.params.id);
if (!post) {
return res.status(404).render('error', {
title: 'Not Found',
message: 'Post not found'
});
}
res.render('post', {
title: post.title,
post
});
});
// 404 handler
app.use((req, res) => {
res.status(404).render('error', {
title: 'Not Found',
message: 'Page not found'
});
});
// Error handler
app.use((err, req, res, next) => {
console.error(err);
res.status(500).render('error', {
title: 'Error',
message: 'Something went wrong'
});
});
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
Sample Data
// data/posts.js
const posts = [
{
id: '1',
title: 'Getting Started with Express',
excerpt: 'Learn the basics of Express.js and how to create your first app.',
content: 'Express is a minimal and flexible Node.js web application framework...',
author: 'John Doe',
publishedAt: '2023-01-15T12:00:00Z'
},
{
id: '2',
title: 'Template Rendering in Express',
excerpt: 'Explore different templating engines and how to use them with Express.',
content: 'Template rendering is a crucial aspect of web development...',
author: 'Jane Smith',
publishedAt: '2023-02-22T14:30:00Z'
}
];
function getPosts() {
return posts;
}
function getPostById(id) {
return posts.find(post => post.id === id);
}
module.exports = {
getPosts,
getPostById
};
Templates
Main layout (views/layouts/main.handlebars
):
Home page (views/home.handlebars
):
Post card partial (views/partials/post-card.handlebars
):
Single post page (views/post.handlebars
):
Best Practices for Template Rendering
-
Keep your templates DRY (Don't Repeat Yourself)
- Use layouts and partials to avoid duplication
-
Separate concerns
- Templates should focus on presentation, not business logic
- Move complex logic to route handlers or helper functions
-
Use consistent naming conventions
- For example, name your templates to match your routes (
users.js
→users.ejs
)
- For example, name your templates to match your routes (
-
Implement error handling
- Always account for missing data or rendering errors
-
Cache templates in production
- Most templating engines support caching to improve performance
-
Be mindful of security
- Use the appropriate escaping mechanisms to prevent XSS attacks
- In EJS, use
<%= %>
for escaped output and<%- %>
only when you need unescaped HTML
-
Consider using a view model pattern
- Create dedicated objects that format data specifically for your views
Summary
Express template rendering allows you to generate dynamic HTML pages using various templating engines. By separating your application logic from your presentation layer, you can create more maintainable and organized code.
In this guide, we've covered:
- The basics of template rendering in Express
- Setting up and configuring different templating engines
- Creating layouts, partials, and templates
- Passing data from routes to templates
- Best practices for working with templates
Template rendering is a fundamental skill for building server-rendered web applications with Express, and mastering this concept will help you create more robust and maintainable web applications.
Additional Resources
- Express Documentation on Template Engines
- EJS Documentation
- Pug Documentation
- Handlebars Documentation
Exercises
- Create a simple Express application that renders a list of products using EJS.
- Convert an existing Express application from one templating engine to another.
- Create a template with conditional rendering that displays different content based on user authentication status.
- Build a multi-page Express application using layouts and partials with your preferred templating engine.
- Create a template helper function that formats currency values and use it in your templates.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)