Skip to main content

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:

  1. Create template files with placeholders for dynamic data
  2. Pass data from your Express routes to these templates
  3. 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:

bash
npm install ejs

Step 2: Configure Express to Use the Templating Engine

In your main Express application file (typically app.js or server.js):

javascript
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:

html
<!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:

javascript
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:

  1. Load the index.ejs template
  2. Replace <%= title %> with "Express Template Rendering"
  3. 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:

html
<!-- 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:

javascript
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:

bash
npm install pug

Configure Express:

javascript
app.set('view engine', 'pug');
app.set('views', path.join(__dirname, 'views'));

Create a template (views/profile.pug):

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:

javascript
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:

bash
npm install express-handlebars

Configure Express:

javascript
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):

handlebars
<!DOCTYPE html>
<html>
<head>
<title>{{title}}</title>
</head>
<body>
<header>
<h1>My Website</h1>
<nav>
<a href="/">Home</a>
<a href="/about">About</a>
</nav>
</header>

<main>
{{{body}}}
</main>

<footer>
<p>&copy; 2023 My Website</p>
</footer>
</body>
</html>

Create a template (views/about.handlebars):

handlebars
<h2>{{title}}</h2>
<p>{{content}}</p>

<h3>Our Team</h3>
<ul>
{{#each team}}
<li>{{this.name}} - {{this.role}}</li>
{{/each}}
</ul>

Render this template:

javascript
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

html
<!-- views/partials/header.ejs -->
<header>
<h1>My Website</h1>
<nav>
<a href="/">Home</a>
<a href="/about">About</a>
</nav>
</header>

Using the partial:

html
<!-- 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:

javascript
const hbs = exphbs.create({
defaultLayout: 'main',
partialsDir: path.join(__dirname, 'views/partials')
});

app.engine('handlebars', hbs.engine);

Create a partial (views/partials/sidebar.handlebars):

handlebars
<div class="sidebar">
<h3>Recent Posts</h3>
<ul>
{{#each posts}}
<li><a href="/post/{{this.id}}">{{this.title}}</a></li>
{{/each}}
</ul>
</div>

Using the partial:

handlebars
<!-- views/home.handlebars -->
<div class="container">
<div class="main-content">
<h2>{{pageTitle}}</h2>
<p>{{pageContent}}</p>
</div>

{{> sidebar}}
</div>

Passing Functions to Templates

You can pass functions to templates for more complex logic:

javascript
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:

html
<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:

javascript
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

javascript
// 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

javascript
// 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):

handlebars
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{title}} - My Blog</title>
<link rel="stylesheet" href="/css/style.css">
</head>
<body>
{{> header}}

<main class="container">
{{{body}}}
</main>

{{> footer}}

<script src="/js/main.js"></script>
</body>
</html>

Home page (views/home.handlebars):

handlebars
<h1>Welcome to My Blog</h1>

<div class="posts">
{{#each posts}}
{{> post-card}}
{{else}}
<p>No posts available.</p>
{{/each}}
</div>

Post card partial (views/partials/post-card.handlebars):

handlebars
<article class="post-card">
<h2><a href="/post/{{this.id}}">{{this.title}}</a></h2>
<p class="meta">By {{this.author}} on {{formatDate this.publishedAt}}</p>
<p class="excerpt">{{this.excerpt}}</p>
<a href="/post/{{this.id}}" class="read-more">Read more</a>
</article>

Single post page (views/post.handlebars):

handlebars
<article class="single-post">
<h1>{{post.title}}</h1>
<p class="meta">By {{post.author}} on {{formatDate post.publishedAt}}</p>

<div class="content">
{{post.content}}
</div>

<a href="/" class="back-link">← Back to all posts</a>
</article>

Best Practices for Template Rendering

  1. Keep your templates DRY (Don't Repeat Yourself)

    • Use layouts and partials to avoid duplication
  2. Separate concerns

    • Templates should focus on presentation, not business logic
    • Move complex logic to route handlers or helper functions
  3. Use consistent naming conventions

    • For example, name your templates to match your routes (users.jsusers.ejs)
  4. Implement error handling

    • Always account for missing data or rendering errors
  5. Cache templates in production

    • Most templating engines support caching to improve performance
  6. 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
  7. 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

Exercises

  1. Create a simple Express application that renders a list of products using EJS.
  2. Convert an existing Express application from one templating engine to another.
  3. Create a template with conditional rendering that displays different content based on user authentication status.
  4. Build a multi-page Express application using layouts and partials with your preferred templating engine.
  5. 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! :)