Skip to main content

Express Handlebars Integration

Introduction

Handlebars is a popular templating engine that can be integrated with Express to generate dynamic HTML content. It's an extension of the Mustache templating language but with added features like helpers, partials, and block expressions. Express Handlebars allows you to separate your HTML markup from your JavaScript logic, making your code more maintainable and easier to understand.

In this tutorial, we'll learn how to set up and use Express Handlebars to create dynamic web pages with reusable templates, which is essential for modern web development.

What is Handlebars?

Handlebars is a logic-less templating engine that uses a template and an input object to generate HTML or other text formats. It's called "logic-less" because there is little to no logic in the templates themselves - most of the logic is handled in your JavaScript code.

Key features of Handlebars include:

  • Minimal logic in templates - keeps your presentation layer clean
  • Partials - reusable template snippets
  • Helpers - functions to manipulate data or create custom output
  • Block expressions - allow you to define custom iterations or conditionals

Getting Started with Express Handlebars

Installation

First, let's create a new Express project and install the necessary packages:

bash
mkdir express-handlebars-demo
cd express-handlebars-demo
npm init -y
npm install express express-handlebars

Basic Setup

Create a file named app.js and set up Express with Handlebars:

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

// Initialize Express app
const app = express();

// Set up Handlebars as the view engine
app.engine('handlebars', engine());
app.set('view engine', 'handlebars');
app.set('views', path.join(__dirname, 'views'));

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

// Define routes
app.get('/', (req, res) => {
res.render('home', {
title: 'Home Page',
message: 'Welcome to Express Handlebars!'
});
});

// Start server
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server is running on port ${PORT}`);
});

Creating Views

Now, let's create the basic directory structure for our views:

bash
mkdir -p views/layouts
touch views/layouts/main.handlebars
touch views/home.handlebars

Create a main layout in views/layouts/main.handlebars:

handlebars
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>{{title}}</title>
<link rel="stylesheet" href="/css/styles.css">
</head>
<body>
<header>
<h1>My Express Handlebars App</h1>
<nav>
<a href="/">Home</a>
<a href="/about">About</a>
</nav>
</header>

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

<footer>
<p>&copy; 2023 Express Handlebars Demo</p>
</footer>
</body>
</html>

Now create the home page view in views/home.handlebars:

handlebars
<div class="container">
<h2>{{title}}</h2>
<p>{{message}}</p>
</div>

Notice the triple curly braces {{{body}}} in the main layout. This is where the content from each individual view will be inserted. Single curly braces {{variable}} are used for rendering variables with HTML escaping, while triple curly braces render without escaping.

Advanced Handlebars Features

Working with Data and Conditionals

Let's create an about route and page that demonstrates conditionals:

javascript
// In app.js, add this route
app.get('/about', (req, res) => {
res.render('about', {
title: 'About Us',
team: [
{ name: 'John', role: 'Developer', active: true },
{ name: 'Jane', role: 'Designer', active: true },
{ name: 'Mike', role: 'Manager', active: false }
],
showTeam: true
});
});

Create views/about.handlebars:

handlebars
<div class="container">
<h2>{{title}}</h2>

{{#if showTeam}}
<h3>Our Team</h3>
<ul class="team-list">
{{#each team}}
<li class="team-member {{#unless active}}inactive{{/unless}}">
<strong>{{name}}</strong> - {{role}}
{{#if active}}
<span class="status">Active</span>
{{else}}
<span class="status">Inactive</span>
{{/if}}
</li>
{{/each}}
</ul>
{{else}}
<p>No team information to display.</p>
{{/if}}
</div>

This template demonstrates:

  • #if/else conditionals
  • #each for iterating over arrays
  • #unless for negated conditions
  • Nested conditionals and expressions

Creating Partials

Partials are reusable template snippets. Let's create a partial for a team member:

bash
mkdir -p views/partials
touch views/partials/teamMember.handlebars

In views/partials/teamMember.handlebars:

handlebars
<li class="team-member {{#unless active}}inactive{{/unless}}">
<strong>{{name}}</strong> - {{role}}
{{#if active}}
<span class="status">Active</span>
{{else}}
<span class="status">Inactive</span>
{{/if}}
</li>

Now, update the about view to use this partial:

handlebars
<div class="container">
<h2>{{title}}</h2>

{{#if showTeam}}
<h3>Our Team</h3>
<ul class="team-list">
{{#each team}}
{{> teamMember}}
{{/each}}
</ul>
{{else}}
<p>No team information to display.</p>
{{/if}}
</div>

The {{> teamMember}} syntax tells Handlebars to include the teamMember partial.

Custom Helpers

Helpers are functions that can transform data or create custom output. Let's register some custom helpers:

javascript
// In app.js, before setting up the engine
const hbs = {
helpers: {
formatDate: function(date) {
return new Date(date).toLocaleDateString();
},
uppercase: function(text) {
return text.toUpperCase();
},
eq: function(a, b) {
return a === b;
}
}
};

// Update the engine setup
app.engine('handlebars', engine(hbs));

Now, let's create a blogs route to demonstrate these helpers:

javascript
app.get('/blogs', (req, res) => {
res.render('blogs', {
title: 'Our Blog',
blogs: [
{
id: 1,
title: 'Getting Started with Express',
author: 'John Doe',
date: '2023-06-15',
featured: true
},
{
id: 2,
title: 'Handlebars Templating',
author: 'Jane Smith',
date: '2023-07-20',
featured: false
}
]
});
});

Create views/blogs.handlebars:

handlebars
<div class="container">
<h2>{{uppercase title}}</h2>

<div class="blogs">
{{#each blogs}}
<article class="blog-post {{#if featured}}featured{{/if}}">
<h3>{{title}}</h3>
<p>By: {{author}} | Published: {{formatDate date}}</p>

{{#if (eq id 1)}}
<span class="badge">First Post</span>
{{/if}}
</article>
{{/each}}
</div>
</div>

This demonstrates:

  • The uppercase helper to transform text
  • The formatDate helper to format dates
  • The eq helper to make equality comparisons

Practical Example: A Product Catalog

Let's build a simple product catalog to showcase a complete example:

javascript
// Add this to app.js
app.get('/products', (req, res) => {
// In a real app, you'd fetch this from a database
const products = [
{
id: 1,
name: 'Laptop',
price: 999.99,
description: 'High-performance laptop for developers',
inStock: true,
categories: ['electronics', 'computers']
},
{
id: 2,
name: 'Smartphone',
price: 699.99,
description: 'Latest model with advanced camera',
inStock: true,
categories: ['electronics', 'phones']
},
{
id: 3,
name: 'Headphones',
price: 199.99,
description: 'Noise-canceling wireless headphones',
inStock: false,
categories: ['electronics', 'accessories']
}
];

// Get category filter from query string
const categoryFilter = req.query.category;

// Filter products if category specified
const filteredProducts = categoryFilter
? products.filter(product => product.categories.includes(categoryFilter))
: products;

res.render('products', {
title: 'Product Catalog',
products: filteredProducts,
categoryFilter,
categories: ['electronics', 'computers', 'phones', 'accessories']
});
});

// Add a product details route
app.get('/products/:id', (req, res) => {
// Normally would fetch from database
const products = [
/* same array as above */
];

const product = products.find(p => p.id === parseInt(req.params.id));

if (!product) {
return res.status(404).render('404', {
title: 'Not Found',
message: 'Product not found'
});
}

res.render('productDetails', {
title: product.name,
product
});
});

// Add the formatCurrency helper
const hbs = {
helpers: {
// ...existing helpers
formatCurrency: function(amount) {
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD'
}).format(amount);
}
}
};

Create views/products.handlebars:

handlebars
<div class="product-catalog">
<h2>{{title}} {{#if categoryFilter}}({{categoryFilter}}){{/if}}</h2>

<div class="filter-section">
<h3>Filter by category:</h3>
<ul class="category-list">
<li><a href="/products">All products</a></li>
{{#each categories}}
<li><a href="/products?category={{this}}" {{#if (eq ../categoryFilter this)}}class="active"{{/if}}>{{this}}</a></li>
{{/each}}
</ul>
</div>

<div class="products-grid">
{{#if products.length}}
{{#each products}}
<div class="product-card">
<h3><a href="/products/{{id}}">{{name}}</a></h3>
<p class="price">{{formatCurrency price}}</p>
<p class="description">{{description}}</p>
<div class="product-footer">
{{#if inStock}}
<span class="stock in-stock">In Stock</span>
{{else}}
<span class="stock out-of-stock">Out of Stock</span>
{{/if}}
<ul class="categories">
{{#each categories}}
<li>{{this}}</li>
{{/each}}
</ul>
</div>
</div>
{{/each}}
{{else}}
<p class="no-products">No products found matching your criteria.</p>
{{/if}}
</div>
</div>

Create views/productDetails.handlebars:

handlebars
<div class="product-details">
<a href="/products" class="back-link">← Back to Products</a>

<div class="product">
<h2>{{product.name}}</h2>
<p class="price">{{formatCurrency product.price}}</p>

<div class="description">
<h3>Description</h3>
<p>{{product.description}}</p>
</div>

<div class="availability">
<h3>Availability</h3>
{{#if product.inStock}}
<p class="in-stock">In Stock - Order Now!</p>
{{else}}
<p class="out-of-stock">Currently Out of Stock</p>
{{/if}}
</div>

<div class="categories">
<h3>Categories</h3>
<ul>
{{#each product.categories}}
<li><a href="/products?category={{this}}">{{this}}</a></li>
{{/each}}
</ul>
</div>
</div>
</div>

And a simple 404 page at views/404.handlebars:

handlebars
<div class="error-page">
<h2>404 - {{title}}</h2>
<p>{{message}}</p>
<a href="/" class="home-link">Go to Homepage</a>
</div>

Configuration Options

Express Handlebars offers several configuration options:

javascript
// Example of extended configuration
app.engine('handlebars', engine({
// Default layout to use
defaultLayout: 'main',

// File extension for templates and layouts
extname: '.handlebars',

// Directories for layouts and partials
layoutsDir: path.join(__dirname, 'views/layouts'),
partialsDir: path.join(__dirname, 'views/partials'),

// Custom helpers
helpers: {
// Helper functions here
}
}));

Summary

In this tutorial, we've covered:

  1. Setting up Express with Handlebars templating
  2. Creating layouts for consistent page structure
  3. Working with Handlebars features like conditionals and iterators
  4. Creating reusable partials for template fragments
  5. Implementing custom helpers to extend functionality
  6. Building a practical product catalog application

Express Handlebars provides a powerful, yet straightforward way to create dynamic web pages with reusable templates. By separating your presentation logic from your business logic, you can create more maintainable and extensible web applications.

Additional Resources

Practice Exercises

  1. Blog System: Extend the blog example to include pagination, categories, and individual blog post pages.

  2. User Dashboard: Create a user profile dashboard with sections for personal information, activity, and settings.

  3. E-commerce Cart: Implement a shopping cart view that lists products, quantities, and calculates totals.

  4. Form Handling: Create a contact form with validation feedback messages using Handlebars templates.

  5. Admin Panel: Build a simple admin panel that allows managing products in the catalog (display, add, edit).

Happy coding!



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