Skip to main content

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:

  1. views: The directory where your template files are located
  2. view engine: The templating engine to use

Here's a basic setup:

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

javascript
// app.js
app.set('view engine', 'ejs');

EJS template example:

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

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

javascript
// app.js
app.set('view engine', 'pug');

Pug template example:

pug
//- 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:

javascript
// app.js
const exphbs = require('express-handlebars');
app.engine('handlebars', exphbs());
app.set('view engine', 'handlebars');

Handlebars template example:

handlebars
<!-- views/user.handlebars -->
<!DOCTYPE html>
<html>
<head>
<title>{{title}}</title>
</head>
<body>
<h1>Welcome, {{username}}!</h1>
{{#if isAdmin}}
<div class="admin-panel">Admin controls here</div>
{{/if}}

<ul>
{{#each items}}
<li>{{this.name}}</li>
{{/each}}
</ul>
</body>
</html>

Advanced View Configuration

Multiple View Engines

You can configure Express to use different template engines for different file extensions:

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

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

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

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

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

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

html
<!-- 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>&copy; 2023 My Blog</p>
</footer>
</body>
</html>

Example single post template:

html
<!-- 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>&copy; 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

html
<!-- 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>&copy; 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

handlebars
<!-- views/layouts/main.handlebars -->
<!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>

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

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

<!-- views/home.handlebars -->
<h1>Welcome to our site!</h1>
<p>This is the home page content.</p>

Error Handling with Views

You can create custom error pages:

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

  1. Set up templating engines to render dynamic content
  2. Choose from various engines like EJS, Pug, or Handlebars
  3. Organize your templates in directories
  4. Use layouts and partials for code reuse
  5. Create custom error pages
  6. 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

Exercises

  1. Configure a new Express application with EJS as the template engine.
  2. Create a simple website with header and footer partials shared across multiple pages.
  3. Set up a custom 404 error page template.
  4. Create a project that uses both EJS and Handlebars template engines for different routes.
  5. 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! :)