Skip to main content

Express Template Partials

Introduction

When building web applications with Express.js, you'll quickly realize that many pages share common elements like headers, footers, navigation bars, and other UI components. Recreating these elements on every page leads to duplicate code, which violates the DRY (Don't Repeat Yourself) principle.

Template partials solve this problem by allowing you to create reusable fragments of templates that can be included in multiple views. Think of them as modular building blocks for your web pages—write once, use everywhere!

In this tutorial, we'll explore:

  • What template partials are and why they're useful
  • How to implement partials in popular templating engines (EJS, Handlebars, and Pug)
  • Best practices for organizing and structuring your partials

What Are Template Partials?

Template partials are reusable template fragments that can be included in other templates. They're particularly useful for components that appear on multiple pages, such as:

  • Headers and footers
  • Navigation menus
  • Sidebar components
  • Authentication forms
  • Alert/notification systems
  • Card layouts

By using partials, you can:

  • Maintain consistency across your website
  • Update components in one place
  • Keep your templates clean and focused
  • Improve developer collaboration

Setting Up Express with Templating Engines

Before we dive into partials, let's make sure we have a basic Express application set up with a templating engine.

javascript
const express = require('express');
const path = require('path');
const app = express();

// Set the view engine (choose one of these)
app.set('view engine', 'ejs'); // for EJS
// app.set('view engine', 'hbs'); // for Handlebars
// app.set('view engine', 'pug'); // for Pug

// Set views directory
app.set('views', path.join(__dirname, 'views'));

// Serve static files
app.use(express.static(path.join(__dirname, 'public')));

app.get('/', (req, res) => {
res.render('index', { title: 'Home Page' });
});

app.listen(3000, () => {
console.log('Server running on port 3000');
});

Now, let's see how to implement partials in different templating engines.

Implementing Partials in EJS

Setting Up EJS Partials

EJS (Embedded JavaScript) offers a straightforward approach to partials using the include function.

First, create a folder structure like this:

views/
├── partials/
│ ├── header.ejs
│ ├── footer.ejs
│ └── navbar.ejs
├── index.ejs
├── about.ejs
└── contact.ejs

Creating Partials

Let's create some basic partials:

  1. views/partials/header.ejs:
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><%= title %></title>
<link rel="stylesheet" href="/css/style.css">
</head>
<body>
  1. views/partials/navbar.ejs:
html
<nav class="navbar">
<div class="logo">
<a href="/">My Website</a>
</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>
  1. views/partials/footer.ejs:
html
  <footer>
<p>&copy; <%= new Date().getFullYear() %> My Website. All rights reserved.</p>
</footer>
<script src="/js/main.js"></script>
</body>
</html>

Using Partials in Templates

Now, let's use these partials in our main view:

views/index.ejs:

html
<%- include('partials/header') %>
<%- include('partials/navbar') %>

<main>
<h1>Welcome to My Website</h1>
<p>This is the home page of my awesome website.</p>
</main>

<%- include('partials/footer') %>

Notice the use of <%- instead of <%= for includes. This tells EJS to render the HTML without escaping it.

Passing Variables to Partials

You can pass variables to your partials as a second parameter to the include function:

html
<%- include('partials/header', {title: 'Custom Page Title'}) %>

If you don't explicitly pass variables, partials can access variables from the parent template.

Implementing Partials in Handlebars

Setting Up Handlebars Partials

For Handlebars, you'll need to register your partials before using them. First, install required packages:

bash
npm install express-handlebars

Then update your Express app:

javascript
const express = require('express');
const { engine } = require('express-handlebars');
const path = require('path');
const app = express();

// Setup Handlebars
app.engine('hbs', engine({
extname: '.hbs',
partialsDir: path.join(__dirname, 'views/partials')
}));
app.set('view engine', 'hbs');
app.set('views', path.join(__dirname, 'views'));

Creating Partials

Create your partials in the views/partials directory:

  1. views/partials/header.hbs:
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{title}}</title>
<link rel="stylesheet" href="/css/style.css">
</head>
<body>
  1. views/partials/navbar.hbs:
html
<nav class="navbar">
<div class="logo">
<a href="/">My Website</a>
</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>
  1. views/partials/footer.hbs:
html
  <footer>
<p>&copy; {{year}} My Website. All rights reserved.</p>
</footer>
<script src="/js/main.js"></script>
</body>
</html>

Using Partials in Templates

Now use these partials in your main template:

views/index.hbs:

html
{{> header}}
{{> navbar}}

<main>
<h1>Welcome to My Website</h1>
<p>This is the home page of my awesome website.</p>
</main>

{{> footer}}

Passing Variables to Handlebars Partials

For dynamic year in the footer, update your route to include it:

javascript
app.get('/', (req, res) => {
res.render('index', {
title: 'Home Page',
year: new Date().getFullYear()
});
});

Implementing Partials in Pug

Setting Up Pug Partials

Pug (formerly Jade) has a different approach to partials using the include and extends keywords.

javascript
const express = require('express');
const app = express();

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

Create a folder structure similar to the others:

views/
├── partials/
│ ├── header.pug
│ ├── footer.pug
│ └── navbar.pug
├── index.pug
├── about.pug
└── contact.pug

Creating Partials

  1. views/partials/header.pug:
pug
doctype html
html(lang="en")
head
meta(charset="UTF-8")
meta(name="viewport", content="width=device-width, initial-scale=1.0")
title= title
link(rel="stylesheet", href="/css/style.css")
body
  1. views/partials/navbar.pug:
pug
nav.navbar
.logo
a(href="/") My Website
ul.nav-links
li
a(href="/") Home
li
a(href="/about") About
li
a(href="/contact") Contact
  1. views/partials/footer.pug:
pug
footer
p &copy; #{new Date().getFullYear()} My Website. All rights reserved.
script(src="/js/main.js")

Using Partials in Templates

There are two main ways to use partials in Pug:

1. Using include:

views/index.pug:

pug
include partials/header
include partials/navbar

main
h1 Welcome to My Website
p This is the home page of my awesome website.

include partials/footer

2. Using layout inheritance with extends and block:

First, create a layout file:

views/layout.pug:

pug
doctype html
html(lang="en")
head
meta(charset="UTF-8")
meta(name="viewport", content="width=device-width, initial-scale=1.0")
title= title
link(rel="stylesheet", href="/css/style.css")
body
include partials/navbar

block content

include partials/footer

Then your page simply extends the layout:

views/index.pug:

pug
extends layout

block content
main
h1 Welcome to My Website
p This is the home page of my awesome website.

This approach is often preferred as it gives more structure to your templates.

Real-World Application: Blog Template

Let's create a more comprehensive example of a blog site using partials. We'll use EJS for this example, but the concepts apply to any templating engine.

Project Structure

views/
├── partials/
│ ├── header.ejs
│ ├── footer.ejs
│ ├── navbar.ejs
│ ├── sidebar.ejs
│ └── post-card.ejs
├── index.ejs
├── post.ejs
└── about.ejs

Creating Advanced Partials

views/partials/post-card.ejs (A reusable blog post preview):

html
<article class="post-card">
<div class="post-image">
<img src="<%= post.imageUrl %>" alt="<%= post.title %>">
</div>
<div class="post-content">
<h3><a href="/post/<%= post.id %>"><%= post.title %></a></h3>
<div class="post-meta">
<span class="date"><%= new Date(post.date).toLocaleDateString() %></span>
<span class="author">By <%= post.author %></span>
</div>
<p><%= post.excerpt %></p>
<a href="/post/<%= post.id %>" class="read-more">Read More</a>
</div>
</article>

views/partials/sidebar.ejs:

html
<aside class="sidebar">
<div class="widget">
<h3>Recent Posts</h3>
<ul>
<% recentPosts.forEach(function(post) { %>
<li><a href="/post/<%= post.id %>"><%= post.title %></a></li>
<% }); %>
</ul>
</div>

<div class="widget">
<h3>Categories</h3>
<ul>
<% categories.forEach(function(category) { %>
<li><a href="/category/<%= category.slug %>"><%= category.name %></a></li>
<% }); %>
</ul>
</div>
</aside>

The Homepage Template

views/index.ejs:

html
<%- include('partials/header', {title: 'My Blog Home'}) %>
<%- include('partials/navbar') %>

<div class="container">
<div class="main-content">
<h1>Latest Blog Posts</h1>

<section class="posts-grid">
<% posts.forEach(function(post) { %>
<%- include('partials/post-card', {post: post}) %>
<% }); %>
</section>

<div class="pagination">
<% if (currentPage > 1) { %>
<a href="/?page=<%= currentPage - 1 %>" class="btn">Previous</a>
<% } %>

<% if (hasNextPage) { %>
<a href="/?page=<%= currentPage + 1 %>" class="btn">Next</a>
<% } %>
</div>
</div>

<%- include('partials/sidebar', {
recentPosts: recentPosts,
categories: categories
}) %>
</div>

<%- include('partials/footer') %>

Express Route

javascript
app.get('/', (req, res) => {
const page = parseInt(req.query.page) || 1;
const postsPerPage = 6;

// Simulating database fetch (in a real app, this would come from a database)
const allPosts = [
{
id: 1,
title: 'Getting Started with Express',
author: 'Jane Doe',
date: '2023-03-15',
imageUrl: '/images/express.jpg',
excerpt: 'Learn how to build web applications with Express.js...'
},
// More posts...
];

const startIndex = (page - 1) * postsPerPage;
const selectedPosts = allPosts.slice(startIndex, startIndex + postsPerPage);

res.render('index', {
title: 'My Blog',
posts: selectedPosts,
currentPage: page,
hasNextPage: startIndex + postsPerPage < allPosts.length,
recentPosts: allPosts.slice(0, 5),
categories: [
{name: 'JavaScript', slug: 'javascript'},
{name: 'Node.js', slug: 'nodejs'},
{name: 'Express', slug: 'express'}
// More categories...
]
});
});

Best Practices for Working with Partials

  1. Organized Directory Structure: Keep your partials in a dedicated directory (like /partials) for easy management.

  2. Naming Conventions: Use clear, consistent naming:

    • Use nouns for component partials (header, footer, card)
    • Consider prefixing with purpose (form-login, modal-signup)
  3. Keep Partials Focused: Each partial should do one thing well - follow the Single Responsibility Principle.

  4. Variable Scope: Be mindful of variable scopes in different template engines. Some engines automatically pass parent variables to partials, others require explicit passing.

  5. Documentation: Add comments at the top of partial files explaining:

    • What the partial does
    • Required variables/parameters
    • Example usage
  6. Conditional Rendering: Make partials flexible with conditional rendering:

ejs
<!-- In header.ejs -->
<head>
<title><%= typeof title !== 'undefined' ? title : 'Default Title' %></title>
<% if (typeof metaDescription !== 'undefined') { %>
<meta name="description" content="<%= metaDescription %>">
<% } %>
</head>
  1. Avoid Nesting Too Deep: While you can include partials within partials, too much nesting can make your code hard to follow.

Common Challenges and Solutions

Challenge 1: Partials Not Finding Files

Problem: You get errors like "partial not found" or "cannot find module".

Solution: Double check paths. Remember that paths are relative to the views directory, not the file including the partial.

Challenge 2: Variables Not Available in Partials

Problem: Variables from parent templates aren't accessible in partials.

Solution: Explicitly pass the needed variables to the partial:

ejs
<%- include('partials/header', {title: pageTitle, user: currentUser}) %>

Challenge 3: Performance with Many Partials

Problem: Too many partials can impact rendering performance.

Solution:

  • Only use partials for truly reusable components
  • Consider caching strategies for your templates
  • Use layout inheritance (extends/blocks) where appropriate

Summary

Template partials are a powerful way to keep your code DRY and maintain consistency across your Express application. They allow you to:

  • Extract reusable components into separate files
  • Maintain consistent elements like headers, footers, and navigation
  • Create modular, maintainable templates
  • Keep your main templates clean and focused

Each templating engine has its own approach to partials, but the core concept remains the same: write once, use everywhere. Whether you're using EJS, Handlebars, Pug, or another engine, mastering partials will significantly improve your development workflow and the quality of your Express applications.

Further Resources and Exercises

Resources

  1. EJS Documentation
  2. Handlebars Documentation
  3. Pug Documentation
  4. Express.js Documentation

Exercises

  1. Basic Implementation: Create a simple Express app with header, footer, and navigation partials using your preferred template engine.

  2. Component Library: Build a UI component library using partials for buttons, cards, forms, and alerts.

  3. Dynamic Navigation: Create a navigation partial that highlights the current page and shows/hides items based on user authentication status.

  4. Multi-Layout System: Implement a system with multiple layouts (e.g., public site, dashboard, admin panel) using partials and template inheritance.

  5. Advanced Blog: Extend the blog example from this tutorial to include comment sections, author profiles, and category pages - all using appropriate partials.



If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)