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:
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:
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:
mkdir -p views/layouts
touch views/layouts/main.handlebars
touch views/home.handlebars
Create a main layout in views/layouts/main.handlebars
:
Now create the home page view in views/home.handlebars
:
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:
// 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
:
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:
mkdir -p views/partials
touch views/partials/teamMember.handlebars
In views/partials/teamMember.handlebars
:
Now, update the about view to use this partial:
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:
// 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:
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
:
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:
// 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
:
Create views/productDetails.handlebars
:
And a simple 404 page at views/404.handlebars
:
Configuration Options
Express Handlebars offers several configuration options:
// 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:
- Setting up Express with Handlebars templating
- Creating layouts for consistent page structure
- Working with Handlebars features like conditionals and iterators
- Creating reusable partials for template fragments
- Implementing custom helpers to extend functionality
- 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
-
Blog System: Extend the blog example to include pagination, categories, and individual blog post pages.
-
User Dashboard: Create a user profile dashboard with sections for personal information, activity, and settings.
-
E-commerce Cart: Implement a shopping cart view that lists products, quantities, and calculates totals.
-
Form Handling: Create a contact form with validation feedback messages using Handlebars templates.
-
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! :)