Express Client-side Templates
Introduction
While server-side templating (like EJS, Pug, or Handlebars) is a common approach in Express applications, client-side templating offers an alternative that can provide more dynamic and responsive user experiences. Client-side templating moves the template rendering process from the server to the browser, which can lead to faster user interactions and reduced server load.
In this guide, we'll explore how to implement client-side templating in Express applications. We'll cover:
- What client-side templating is and how it differs from server-side templating
- Popular client-side templating libraries
- How to set up client-side templates with Express
- Practical examples and real-world applications
Client-side vs Server-side Templating
Before diving into implementation, let's understand the key differences:
Server-side Templating | Client-side Templating |
---|---|
Templates rendered on server | Templates rendered in browser |
Full page refresh for updates | Partial updates without page refresh |
Less JavaScript dependency | Requires JavaScript to function |
Initial load may be faster | Better for dynamic interactions |
Popular Client-side Templating Libraries
Several libraries can be used for client-side templating in Express applications:
- Handlebars.js: A popular and powerful templating engine
- Mustache.js: Simple, logic-less templates
- Underscore/Lodash templates: Simple templating from utility libraries
- React: Component-based UI library (more complex but powerful)
- Vue.js: Progressive framework with great templating system
In this tutorial, we'll focus on Handlebars as it has both server and client-side implementations, making it a great choice for hybrid applications.
Setting Up Client-side Templates in Express
Step 1: Install Required Packages
First, let's set up a basic Express application with the necessary packages:
npm init -y
npm install express handlebars express-handlebars
Step 2: Create Basic Express Server
Now, let's create a basic Express server (app.js
):
const express = require('express');
const path = require('path');
const exphbs = require('express-handlebars');
const app = express();
const PORT = process.env.PORT || 3000;
// Set up static file serving
app.use(express.static(path.join(__dirname, 'public')));
// Setup handlebars as the view engine
app.engine('handlebars', exphbs.engine({
defaultLayout: 'main',
layoutsDir: path.join(__dirname, 'views/layouts')
}));
app.set('view engine', 'handlebars');
app.set('views', path.join(__dirname, 'views'));
// Routes
app.get('/', (req, res) => {
res.render('home', {
title: 'Client-side Templates Example',
serverRendered: new Date().toLocaleTimeString()
});
});
app.get('/api/data', (req, res) => {
// This endpoint will return data for our client-side template
res.json({
items: [
{ id: 1, name: 'Item 1', description: 'First item description' },
{ id: 2, name: 'Item 2', description: 'Second item description' },
{ id: 3, name: 'Item 3', description: 'Third item description' }
],
timestamp: new Date().toLocaleTimeString()
});
});
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
Step 3: Create Your Views
Let's create a basic layout file (views/layouts/main.handlebars
):
<!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>
<div class="container">
<header>
<h1>{{title}}</h1>
<p>Server rendered at: {{serverRendered}}</p>
</header>
{{{body}}}
<footer>
<p>© 2023 - Client-side Templates Demo</p>
</footer>
</div>
<!-- Include Handlebars from CDN -->
<script src="https://cdn.jsdelivr.net/npm/handlebars@latest/dist/handlebars.js"></script>
<!-- Include our JavaScript -->
<script src="/js/main.js"></script>
</body>
</html>
Now create the home view (views/home.handlebars
):
<div class="content">
<h2>Client-side Templating Demo</h2>
<button id="load-data">Load Data</button>
<div id="results">
<!-- This is where client-side templates will render -->
</div>
<!-- Client-side template definition -->
<script id="items-template" type="text/x-handlebars-template">
<div class="items-container">
<p>Data refreshed at: {{timestamp}}</p>
<ul class="items-list">
{{#each items}}
<li class="item" data-id="{{id}}">
<h3>{{name}}</h3>
<p>{{description}}</p>
</li>
{{/each}}
</ul>
</div>
</script>
</div>
Step 4: Create Your Client-side JavaScript
Create a new file at public/js/main.js
:
document.addEventListener('DOMContentLoaded', () => {
// Compile the template
const source = document.getElementById('items-template').innerHTML;
const template = Handlebars.compile(source);
// Set up event listener
document.getElementById('load-data').addEventListener('click', async () => {
try {
const response = await fetch('/api/data');
const data = await response.json();
// Render the template with the data
const resultsContainer = document.getElementById('results');
resultsContainer.innerHTML = template(data);
// Add animation class to highlight new content
resultsContainer.classList.add('updated');
setTimeout(() => {
resultsContainer.classList.remove('updated');
}, 500);
} catch (error) {
console.error('Error loading data:', error);
document.getElementById('results').innerHTML = '<p class="error">Failed to load data</p>';
}
});
});
Step 5: Add Some Basic CSS
Create a new file at public/css/style.css
:
body {
font-family: Arial, sans-serif;
line-height: 1.6;
margin: 0;
padding: 0;
color: #333;
}
.container {
max-width: 1100px;
margin: 0 auto;
padding: 20px;
}
header {
background: #f4f4f4;
padding: 20px;
margin-bottom: 20px;
border-radius: 5px;
}
button {
background: #4CAF50;
color: white;
border: none;
padding: 10px 15px;
cursor: pointer;
font-size: 16px;
margin-bottom: 20px;
border-radius: 4px;
}
button:hover {
background: #45a049;
}
.items-list {
list-style: none;
padding: 0;
}
.item {
background: #f9f9f9;
margin-bottom: 10px;
padding: 15px;
border-radius: 4px;
border-left: 4px solid #4CAF50;
}
.updated {
animation: highlight 0.5s ease-in-out;
}
@keyframes highlight {
0% { background-color: #ffff99; }
100% { background-color: transparent; }
}
How It Works
When a user visits the page:
- Express server renders the initial page using server-side Handlebars
- The page loads with basic structure and a button to load data
- When the user clicks "Load Data":
- JavaScript makes an AJAX request to
/api/data
- The server responds with JSON data
- The client-side Handlebars template compiles using that JSON data
- The result is inserted into the DOM without a page refresh
- JavaScript makes an AJAX request to
Advanced Example: Dynamic Filtering
Let's enhance our example to include dynamic filtering of the items:
Modify the public/js/main.js
file:
document.addEventListener('DOMContentLoaded', () => {
// Compile the template
const source = document.getElementById('items-template').innerHTML;
const template = Handlebars.compile(source);
let currentData = null;
// Function to load data
const loadData = async () => {
try {
const response = await fetch('/api/data');
currentData = await response.json();
renderItems(currentData);
} catch (error) {
console.error('Error loading data:', error);
document.getElementById('results').innerHTML = '<p class="error">Failed to load data</p>';
}
};
// Function to render items based on filter
const renderItems = (data, filter = '') => {
const filteredData = {
...data,
items: data.items.filter(item =>
filter === '' ||
item.name.toLowerCase().includes(filter.toLowerCase()) ||
item.description.toLowerCase().includes(filter.toLowerCase())
)
};
const resultsContainer = document.getElementById('results');
resultsContainer.innerHTML = template(filteredData);
};
// Set up event listeners
document.getElementById('load-data').addEventListener('click', loadData);
// Add search functionality
const searchInput = document.createElement('input');
searchInput.type = 'text';
searchInput.placeholder = 'Filter items...';
searchInput.classList.add('search-input');
document.querySelector('.content').insertBefore(
searchInput,
document.getElementById('results')
);
searchInput.addEventListener('input', (e) => {
if (currentData) {
renderItems(currentData, e.target.value);
}
});
});
Update the CSS in public/css/style.css
:
.search-input {
width: 100%;
padding: 10px;
margin-bottom: 15px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 16px;
}
With this enhancement, users can now filter items in real-time without any server requests.
Real-world Applications
Client-side templating is particularly useful in:
- Single-Page Applications (SPAs) - For seamless navigation without full page reloads
- Dashboards & Admin Interfaces - For real-time data visualization
- Interactive Forms - For immediate feedback and validation
- E-commerce Sites - For product filtering, sorting, and cart management
- Social Media Feeds - For dynamic content loading and interaction
Best Practices
When implementing client-side templates in Express:
- Provide Progressive Enhancement - Make sure basic functionality works without JavaScript
- Consider SEO - Search engines may not execute your JavaScript templates
- Bundle Templates Efficiently - Precompile templates when possible to improve performance
- Mind the Data Size - Large datasets can slow down client-side rendering
- Error Handling - Always have fallback UI for failed API requests
- Security - Be careful with user input to avoid XSS vulnerabilities
Summary
Client-side templating in Express offers a powerful way to create dynamic user interfaces without full page refreshes. By moving the template rendering process to the browser, you can create more responsive applications that update smoothly.
Key points to remember:
- Client-side templating complements Express's server capabilities
- Libraries like Handlebars work both on server and client
- The approach reduces server load and improves user experience
- It's ideal for interactive applications requiring frequent updates
- Consider progressive enhancement for users without JavaScript
Additional Resources
Exercises
- Add a feature to edit items in the list using client-side templates
- Implement client-side pagination for the items list
- Create a "detail view" template that shows when an item is clicked
- Add animation effects when items are added or removed
- Implement a hybrid approach where initial data comes server-rendered but updates use client-side templating
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)