Express View Configuration
Introduction
When building web applications with Express.js, you'll often need to display dynamic content that changes based on user interaction, database data, or other factors. While you could manually construct HTML strings in your route handlers, this quickly becomes unwieldy and difficult to maintain. This is where view engines come in.
Express view configuration allows you to set up templating engines that dynamically generate HTML content from templates and data. This separation of concerns keeps your application logic clean and your presentation layer organized.
In this guide, we'll explore how to configure views in Express applications, set up different templating engines, and understand the view resolution process.
Setting Up View Configuration
Basic View Configuration
To use templates in Express, you need to configure two settings:
views
: The directory where your template files are locatedview engine
: The templating engine to use
Here's a basic setup:
const express = require('express');
const path = require('path');
const app = express();
// Set the views directory
app.set('views', path.join(__dirname, 'views'));
// Set the view engine to use
app.set('view engine', 'ejs');
app.get('/', (req, res) => {
res.render('home', { title: 'Express View Engine' });
});
app.listen(3000, () => {
console.log('Server started on port 3000');
});
In this example:
- We tell Express to look for template files in the
views
directory - We set EJS as our templating engine
- In our route handler, we use
res.render()
to render thehome
template with a data object
Directory Structure
A typical Express application using views might have a directory structure like this:
my-express-app/
│
├── views/
│ ├── home.ejs
│ ├── about.ejs
│ └── partials/
│ ├── header.ejs
│ └── footer.ejs
│
├── public/
│ ├── css/
│ ├── js/
│ └── images/
│
├── app.js
└── package.json
Choosing a View Engine
Express is compatible with many templating engines. Here are some popular choices:
EJS (Embedded JavaScript)
EJS allows you to embed JavaScript directly in your templates:
// app.js
app.set('view engine', 'ejs');
EJS template example:
<!-- views/user.ejs -->
<!DOCTYPE html>
<html>
<head>
<title><%= title %></title>
</head>
<body>
<h1>Welcome, <%= username %>!</h1>
<% if (isAdmin) { %>
<div class="admin-panel">Admin controls here</div>
<% } %>
<ul>
<% items.forEach(function(item) { %>
<li><%= item.name %></li>
<% }); %>
</ul>
</body>
</html>
To render this template:
app.get('/user', (req, res) => {
res.render('user', {
title: 'User Profile',
username: 'JohnDoe',
isAdmin: true,
items: [
{ name: 'Item 1' },
{ name: 'Item 2' },
{ name: 'Item 3' }
]
});
});
Pug (formerly Jade)
Pug uses significant whitespace and a shorter syntax:
// app.js
app.set('view engine', 'pug');
Pug template example:
//- views/user.pug
doctype html
html
head
title= title
body
h1 Welcome, #{username}!
if isAdmin
.admin-panel Admin controls here
ul
each item in items
li= item.name
Handlebars
Handlebars uses minimal logic in templates:
// app.js
const exphbs = require('express-handlebars');
app.engine('handlebars', exphbs());
app.set('view engine', 'handlebars');
Handlebars template example:
Advanced View Configuration
Multiple View Engines
You can configure Express to use different template engines for different file extensions:
const express = require('express');
const exphbs = require('express-handlebars');
const app = express();
// Set up Handlebars for .handlebars files
app.engine('handlebars', exphbs());
// Set up EJS for .ejs files
app.set('view engine', 'ejs'); // Default engine
app.get('/handlebars-example', (req, res) => {
res.render('example.handlebars', { title: 'Handlebars Example' });
});
app.get('/ejs-example', (req, res) => {
res.render('example.ejs', { title: 'EJS Example' });
});
Custom Template Engine Options
Most template engines accept configuration options:
// For EJS
app.set('view engine', 'ejs');
app.set('view options', {
delimiter: '?', // Change the delimiter from % to ?
openDelimiter: '{', // Change the open delimiter from < to {
closeDelimiter: '}' // Change the close delimiter from > to }
});
// For Handlebars
const hbs = exphbs.create({
defaultLayout: 'main',
extname: '.hbs',
helpers: {
formatDate: function(date) {
return new Date(date).toLocaleDateString();
}
}
});
app.engine('hbs', hbs.engine);
app.set('view engine', 'hbs');
Multiple View Directories
You can set up multiple view directories:
const express = require('express');
const path = require('path');
const app = express();
// Set multiple view directories
app.set('views', [
path.join(__dirname, 'views'),
path.join(__dirname, 'admin-views'),
path.join(__dirname, 'email-templates')
]);
app.set('view engine', 'ejs');
app.get('/admin', (req, res) => {
// Express will search for dashboard.ejs in all directories
res.render('dashboard');
});
View Caching
By default, Express caches compiled templates in production mode, which improves performance. You can control this behavior:
// Enable view caching (default in production)
app.set('view cache', true);
// Disable view caching (useful for development)
app.set('view cache', false);
A common pattern is to disable caching in development:
const isDev = process.env.NODE_ENV !== 'production';
app.set('view cache', !isDev);
Practical Example: Building a Blog
Let's build a simple blog application to demonstrate how to use views in a real-world scenario:
const express = require('express');
const path = require('path');
const app = express();
// Configure views
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');
// Mock database
const blogPosts = [
{
id: 1,
title: 'Introduction to Express',
content: 'Express is a minimal web framework for Node.js...',
author: 'John Doe',
date: new Date(2023, 0, 15)
},
{
id: 2,
title: 'Using Template Engines',
content: 'Template engines allow you to generate HTML dynamically...',
author: 'Jane Smith',
date: new Date(2023, 1, 20)
}
];
// Home route - display all blog posts
app.get('/', (req, res) => {
res.render('blog/index', {
title: 'My Blog',
posts: blogPosts
});
});
// Single post route
app.get('/post/:id', (req, res) => {
const post = blogPosts.find(p => p.id === parseInt(req.params.id));
if (!post) {
return res.status(404).render('error', {
message: 'Post not found'
});
}
res.render('blog/post', { title: post.title, post });
});
app.listen(3000, () => {
console.log('Blog app listening on port 3000');
});
Example blog index template:
<!-- views/blog/index.ejs -->
<!DOCTYPE html>
<html>
<head>
<title><%= title %></title>
<link rel="stylesheet" href="/css/style.css">
</head>
<body>
<header>
<h1><%= title %></h1>
</header>
<main>
<% posts.forEach(function(post) { %>
<article class="post-preview">
<h2><a href="/post/<%= post.id %>"><%= post.title %></a></h2>
<div class="meta">
By <%= post.author %> on <%= post.date.toDateString() %>
</div>
<p><%= post.content.substring(0, 100) %>...</p>
<a href="/post/<%= post.id %>">Read more</a>
</article>
<% }); %>
</main>
<footer>
<p>© 2023 My Blog</p>
</footer>
</body>
</html>
Example single post template:
<!-- views/blog/post.ejs -->
<!DOCTYPE html>
<html>
<head>
<title><%= title %></title>
<link rel="stylesheet" href="/css/style.css">
</head>
<body>
<header>
<h1><%= post.title %></h1>
<div class="meta">
By <%= post.author %> on <%= post.date.toDateString() %>
</div>
</header>
<main>
<article>
<p><%= post.content %></p>
</article>
<a href="/">Back to all posts</a>
</main>
<footer>
<p>© 2023 My Blog</p>
</footer>
</body>
</html>
Template Layouts and Partials
Most templating engines support layouts and partials to avoid code duplication:
Partials in EJS
<!-- views/partials/header.ejs -->
<!DOCTYPE html>
<html>
<head>
<title><%= title %></title>
<link rel="stylesheet" href="/css/style.css">
</head>
<body>
<header>
<h1>My Website</h1>
<nav>
<ul>
<li><a href="/">Home</a></li>
<li><a href="/about">About</a></li>
<li><a href="/contact">Contact</a></li>
</ul>
</nav>
</header>
<!-- views/partials/footer.ejs -->
<footer>
<p>© 2023 My Website</p>
</footer>
</body>
</html>
<!-- views/home.ejs -->
<%- include('partials/header') %>
<main>
<h1>Welcome to our site!</h1>
<p>This is the home page content.</p>
</main>
<%- include('partials/footer') %>
Layouts in Handlebars
Error Handling with Views
You can create custom error pages:
app.use((req, res) => {
// Handle 404
res.status(404).render('errors/404', {
title: 'Page Not Found',
url: req.url
});
});
app.use((err, req, res, next) => {
// Handle 500
console.error(err);
res.status(500).render('errors/500', {
title: 'Server Error',
error: process.env.NODE_ENV === 'production' ? {} : err
});
});
Summary
Express view configuration allows you to:
- Set up templating engines to render dynamic content
- Choose from various engines like EJS, Pug, or Handlebars
- Organize your templates in directories
- Use layouts and partials for code reuse
- Create custom error pages
- Optimize performance through caching
By properly configuring views in your Express application, you can maintain a clean separation between your application logic and presentation, making your code more maintainable and scalable.
Additional Resources
- Express.js Documentation on Using Template Engines
- EJS Documentation
- Pug Documentation
- Handlebars Documentation
Exercises
- Configure a new Express application with EJS as the template engine.
- Create a simple website with header and footer partials shared across multiple pages.
- Set up a custom 404 error page template.
- Create a project that uses both EJS and Handlebars template engines for different routes.
- Build a product catalog that displays products from an array and allows viewing individual product details.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)