Express Template Layouts
Introduction
When building web applications with Express.js, you'll quickly realize that many of your pages share common elements like headers, footers, navigation bars, and other UI components. Copying these elements across multiple template files leads to code duplication, making your application harder to maintain.
This is where template layouts come in. Layouts provide a way to define a common structure for your web pages while allowing you to inject page-specific content into that structure. Using layouts helps you maintain consistency across your application while adhering to the DRY (Don't Repeat Yourself) principle.
In this tutorial, we'll learn how to implement layout functionality with different templating engines in Express.js.
Understanding Template Layouts
A layout is essentially a master template that defines the overall structure of your pages. It contains placeholders where page-specific content can be inserted. This approach separates the page structure from its content, making your templates more modular and easier to maintain.
Before diving into specific implementations, let's understand the basic concept with a simple diagram:
Layout Template
┌─────────────────────────────┐
│ Header │
├─────────────────────────────┤
│ │
│ {Content Placeholder} │ ← Content from page-specific templates is injected here
│ │
├─────────────────────────────┤
│ Footer │
└─────────────────────────────┘
Implementing Layouts in Different Template Engines
Let's explore how to implement layouts in three popular templating engines used with Express: EJS, Pug, and Handlebars.
Layouts in EJS
EJS doesn't have built-in layout support, but we can implement it using the ejs-layouts
package.
First, install the required packages:
npm install ejs express-ejs-layouts
Now, set up your Express application to use EJS with layouts:
const express = require('express');
const expressLayouts = require('express-ejs-layouts');
const app = express();
// Set EJS as the view engine
app.set('view engine', 'ejs');
// Use express-ejs-layouts middleware
app.use(expressLayouts);
// Optional: Set the default layout
app.set('layout', 'layouts/default');
app.get('/', (req, res) => {
res.render('home', { title: 'Home Page' });
});
app.listen(3000, () => {
console.log('Server started on port 3000');
});
Create a layout file at views/layouts/default.ejs
:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title><%= title %></title>
<link rel="stylesheet" href="/css/style.css">
<%- defineContent('head') %>
</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>© <%= new Date().getFullYear() %> My Website</p>
</footer>
</body>
</html>
Now create a page template at views/home.ejs
:
<% contentFor('head') %>
<meta name="description" content="Welcome to our homepage">
<h2>Welcome to the Home Page</h2>
<p>This is the main content of the home page.</p>
<div class="featured">
<h3>Featured Content</h3>
<p>Check out our latest articles and updates!</p>
</div>
When you visit the home page, EJS will insert the content from home.ejs
into the <%- body %>
section of the layout template, and any content defined with contentFor('head')
will be placed where <%- defineContent('head') %>
appears in the layout.
Layouts in Pug
Pug (formerly Jade) has built-in layout support through its inheritance system.
First, install Pug:
npm install pug
Set up Express to use Pug:
const express = require('express');
const app = express();
// Set Pug as the view engine
app.set('view engine', 'pug');
app.get('/', (req, res) => {
res.render('home', { title: 'Home Page' });
});
app.listen(3000, () => {
console.log('Server started on port 3000');
});
Create a layout file at views/layout.pug
:
doctype html
html
head
meta(charset="utf-8")
title= title
link(rel="stylesheet", href="/css/style.css")
block head
body
header
h1 My Website
nav
ul
li
a(href="/") Home
li
a(href="/about") About
li
a(href="/contact") Contact
main
block content
footer
p © #{new Date().getFullYear()} My Website
Now create a page template at views/home.pug
:
extends layout
block head
meta(name="description", content="Welcome to our homepage")
block content
h2 Welcome to the Home Page
p This is the main content of the home page.
.featured
h3 Featured Content
p Check out our latest articles and updates!
Pug uses extends
to inherit from the layout, and block
to define sections that can be overridden in child templates.
Layouts in Handlebars
Handlebars supports layouts through the express-handlebars
package.
First, install the required packages:
npm install express-handlebars
Set up Express to use Handlebars with layouts:
const express = require('express');
const { engine } = require('express-handlebars');
const app = express();
// Set up handlebars with layouts
app.engine('handlebars', engine({
defaultLayout: 'main'
}));
app.set('view engine', 'handlebars');
app.get('/', (req, res) => {
res.render('home', { title: 'Home Page' });
});
app.listen(3000, () => {
console.log('Server started on port 3000');
});
Create a layout file at views/layouts/main.handlebars
:
Create a page template at views/home.handlebars
:
To provide the year in the footer, modify your route handler:
app.get('/', (req, res) => {
res.render('home', {
title: 'Home Page',
year: new Date().getFullYear()
});
});
Advanced Layout Techniques
Nested Layouts
For more complex applications, you might need nested layouts. For example, you might have a basic layout for all pages, but also want specific layouts for admin pages or authenticated user pages.
Nested Layouts in Pug
Pug makes this straightforward with its inheritance system:
// views/layout.pug (Base layout)
doctype html
html
head
title= title
block head
body
block body
footer
p © #{new Date().getFullYear()} My Website
// views/layouts/admin.pug (Admin layout)
extends ../layout
block body
header
h1 Admin Dashboard
nav
ul
li
a(href="/admin") Dashboard
li
a(href="/admin/users") Users
main
block content
// views/admin/users.pug (Admin page)
extends ../layouts/admin
block content
h2 User Management
table
tr
th Username
th Email
th Actions
// User rows would go here
Partial Templates
Partials are reusable template fragments that can be included in multiple pages or layouts. They're useful for components like navigation bars, sidebars, or card elements.
Partials in EJS
// app.js
app.set('view engine', 'ejs');
Create a partial at views/partials/navbar.ejs
:
<nav class="navbar">
<div class="brand">My App</div>
<ul class="nav-links">
<li><a href="/">Home</a></li>
<li><a href="/about">About</a></li>
<li><a href="/contact">Contact</a></li>
</ul>
</nav>
Include it in your layout:
<!-- views/layouts/default.ejs -->
<!DOCTYPE html>
<html>
<head>
<title><%= title %></title>
</head>
<body>
<%- include('../partials/navbar') %>
<main>
<%- body %>
</main>
<footer>
<p>© <%= new Date().getFullYear() %> My Website</p>
</footer>
</body>
</html>
Real-World Example: Blog Application
Let's put everything together in a more complete example of a simple blog application using EJS layouts.
Project Structure
blog-app/
├── app.js
├── package.json
├── public/
│ └── css/
│ └── style.css
└── views/
├── layouts/
│ └── default.ejs
├── partials/
│ ├── navbar.ejs
│ └── sidebar.ejs
├── index.ejs
├── post.ejs
└── about.ejs
Implementation
First, set up your Express app:
// app.js
const express = require('express');
const expressLayouts = require('express-ejs-layouts');
const path = require('path');
const app = express();
// Static files
app.use(express.static(path.join(__dirname, 'public')));
// View engine setup
app.set('view engine', 'ejs');
app.use(expressLayouts);
app.set('layout', 'layouts/default');
// Sample blog post data
const posts = [
{
id: 1,
title: 'Getting Started with Express',
content: 'Express is a minimal and flexible Node.js web application framework...',
author: 'John Doe',
date: '2023-05-15'
},
{
id: 2,
title: 'Template Engines in Express',
content: 'Template engines allow you to use static template files in your application...',
author: 'Jane Smith',
date: '2023-05-20'
}
];
// Routes
app.get('/', (req, res) => {
res.render('index', {
title: 'Express Blog',
posts: posts
});
});
app.get('/post/:id', (req, res) => {
const post = posts.find(p => p.id === parseInt(req.params.id));
if (!post) return res.status(404).send('Post not found');
res.render('post', {
title: post.title,
post: post
});
});
app.get('/about', (req, res) => {
res.render('about', { title: 'About Us' });
});
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server started on port ${PORT}`);
});
Create the default layout:
<!-- views/layouts/default.ejs -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><%= title %> | Express Blog</title>
<link rel="stylesheet" href="/css/style.css">
<%- defineContent('head') %>
</head>
<body>
<%- include('../partials/navbar') %>
<div class="container">
<div class="main-content">
<%- body %>
</div>
<div class="sidebar">
<%- include('../partials/sidebar') %>
</div>
</div>
<footer>
<div class="container">
<p>© <%= new Date().getFullYear() %> Express Blog. All rights reserved.</p>
</div>
</footer>
</body>
</html>
Create the partials:
<!-- views/partials/navbar.ejs -->
<header>
<div class="container">
<div class="logo">
<a href="/">Express Blog</a>
</div>
<nav>
<ul>
<li><a href="/">Home</a></li>
<li><a href="/about">About</a></li>
</ul>
</nav>
</div>
</header>
<!-- views/partials/sidebar.ejs -->
<div class="widget">
<h3>Recent Posts</h3>
<ul>
<% posts.slice(0, 3).forEach(post => { %>
<li>
<a href="/post/<%= post.id %>"><%= post.title %></a>
</li>
<% }) %>
</ul>
</div>
<div class="widget">
<h3>Categories</h3>
<ul>
<li><a href="#">JavaScript</a></li>
<li><a href="#">Node.js</a></li>
<li><a href="#">Express</a></li>
<li><a href="#">Web Development</a></li>
</ul>
</div>
Create the page templates:
<!-- views/index.ejs -->
<% contentFor('head') %>
<meta name="description" content="Welcome to the Express Blog, featuring articles on web development">
<h1>Latest Blog Posts</h1>
<div class="posts">
<% posts.forEach(post => { %>
<div class="post-card">
<h2><a href="/post/<%= post.id %>"><%= post.title %></a></h2>
<div class="post-meta">
<span class="author"><%= post.author %></span>
<span class="date"><%= post.date %></span>
</div>
<p><%= post.content.substring(0, 150) %>...</p>
<a href="/post/<%= post.id %>" class="read-more">Read More</a>
</div>
<% }) %>
</div>
<!-- views/post.ejs -->
<% contentFor('head') %>
<meta name="description" content="<%= post.content.substring(0, 160) %>">
<div class="single-post">
<h1><%= post.title %></h1>
<div class="post-meta">
<span class="author">By <%= post.author %></span>
<span class="date">Published on <%= post.date %></span>
</div>
<div class="post-content">
<p><%= post.content %></p>
</div>
<div class="post-navigation">
<a href="/" class="back">← Back to all posts</a>
</div>
</div>
<!-- views/about.ejs -->
<% contentFor('head') %>
<meta name="description" content="Learn more about Express Blog and our mission">
<h1>About Express Blog</h1>
<div class="about-content">
<p>Welcome to Express Blog, a platform dedicated to sharing knowledge about web development, Node.js, and the Express framework.</p>
<p>Our mission is to provide high-quality, practical tutorials and articles that help developers build better web applications.</p>
<h2>Our Team</h2>
<div class="team-members">
<div class="member">
<h3>John Doe</h3>
<p>Lead Developer & Author</p>
</div>
<div class="member">
<h3>Jane Smith</h3>
<p>Content Editor & Developer Advocate</p>
</div>
</div>
</div>
This example demonstrates how layouts, partials, and page-specific templates work together to create a consistent yet flexible blog application.
Summary
Template layouts are an essential tool for building maintainable Express applications with consistent design patterns. They help you:
- Maintain consistency across your application's user interface
- Reduce code duplication by defining common elements once
- Separate concerns by dividing page structure from content
- Improve maintainability by centralizing layout changes
Each templating engine has its own approach to layouts:
- EJS uses the
express-ejs-layouts
package with body and content sections - Pug uses template inheritance with
extends
andblock
directives - Handlebars uses the
express-handlebars
package with a defaultLayout option
By combining layouts with partials and page-specific templates, you can create modular, maintainable view structures for your Express applications.
Additional Resources and Exercises
Resources
- EJS documentation
- Pug documentation
- Handlebars documentation
- Express-EJS-Layouts on GitHub
- Express-Handlebars on GitHub
Exercises
-
Basic Layout Implementation: Convert an existing Express application that doesn't use layouts to use one of the layout approaches described in this tutorial.
-
Multiple Layouts: Create an Express application with different layouts for public pages and admin pages.
-
Dynamic Layout Selection: Modify an Express route to choose different layouts based on user preferences or device type (mobile vs. desktop).
-
Content Sections: Implement a layout with multiple content sections (e.g., main content, sidebar content, scripts section) and create pages that populate these different sections.
-
Advanced Blog: Extend the blog example with features like pagination, comments, and category filtering, all while maintaining a consistent layout structure.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)