Skip to main content

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:

bash
npm install ejs express-ejs-layouts

Now, set up your Express application to use EJS with layouts:

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

html
<!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>&copy; <%= new Date().getFullYear() %> My Website</p>
</footer>
</body>
</html>

Now create a page template at views/home.ejs:

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

bash
npm install pug

Set up Express to use Pug:

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

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 &copy; #{new Date().getFullYear()} My Website

Now create a page template at views/home.pug:

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:

bash
npm install express-handlebars

Set up Express to use Handlebars with layouts:

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

handlebars
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>{{title}}</title>
<link rel="stylesheet" href="/css/style.css">
{{{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>&copy; {{year}} My Website</p>
</footer>
</body>
</html>

Create a page template at views/home.handlebars:

handlebars
<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>

To provide the year in the footer, modify your route handler:

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

pug
// views/layout.pug (Base layout)
doctype html
html
head
title= title
block head
body
block body
footer
p &copy; #{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

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

Create a partial at views/partials/navbar.ejs:

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

html
<!-- views/layouts/default.ejs -->
<!DOCTYPE html>
<html>
<head>
<title><%= title %></title>
</head>
<body>
<%- include('../partials/navbar') %>

<main>
<%- body %>
</main>

<footer>
<p>&copy; <%= 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:

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

html
<!-- 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>&copy; <%= new Date().getFullYear() %> Express Blog. All rights reserved.</p>
</div>
</footer>
</body>
</html>

Create the partials:

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

html
<!-- 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">&larr; 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:

  1. Maintain consistency across your application's user interface
  2. Reduce code duplication by defining common elements once
  3. Separate concerns by dividing page structure from content
  4. 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 and block 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

  1. EJS documentation
  2. Pug documentation
  3. Handlebars documentation
  4. Express-EJS-Layouts on GitHub
  5. Express-Handlebars on GitHub

Exercises

  1. Basic Layout Implementation: Convert an existing Express application that doesn't use layouts to use one of the layout approaches described in this tutorial.

  2. Multiple Layouts: Create an Express application with different layouts for public pages and admin pages.

  3. Dynamic Layout Selection: Modify an Express route to choose different layouts based on user preferences or device type (mobile vs. desktop).

  4. 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.

  5. 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! :)