Skip to main content

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 TemplatingClient-side Templating
Templates rendered on serverTemplates rendered in browser
Full page refresh for updatesPartial updates without page refresh
Less JavaScript dependencyRequires JavaScript to function
Initial load may be fasterBetter for dynamic interactions

Several libraries can be used for client-side templating in Express applications:

  1. Handlebars.js: A popular and powerful templating engine
  2. Mustache.js: Simple, logic-less templates
  3. Underscore/Lodash templates: Simple templating from utility libraries
  4. React: Component-based UI library (more complex but powerful)
  5. 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:

bash
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):

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

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>
<div class="container">
<header>
<h1>{{title}}</h1>
<p>Server rendered at: {{serverRendered}}</p>
</header>

{{{body}}}

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

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

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

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:

  1. Express server renders the initial page using server-side Handlebars
  2. The page loads with basic structure and a button to load data
  3. 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

Advanced Example: Dynamic Filtering

Let's enhance our example to include dynamic filtering of the items:

Modify the public/js/main.js file:

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

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:

  1. Single-Page Applications (SPAs) - For seamless navigation without full page reloads
  2. Dashboards & Admin Interfaces - For real-time data visualization
  3. Interactive Forms - For immediate feedback and validation
  4. E-commerce Sites - For product filtering, sorting, and cart management
  5. Social Media Feeds - For dynamic content loading and interaction

Best Practices

When implementing client-side templates in Express:

  1. Provide Progressive Enhancement - Make sure basic functionality works without JavaScript
  2. Consider SEO - Search engines may not execute your JavaScript templates
  3. Bundle Templates Efficiently - Precompile templates when possible to improve performance
  4. Mind the Data Size - Large datasets can slow down client-side rendering
  5. Error Handling - Always have fallback UI for failed API requests
  6. 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

  1. Add a feature to edit items in the list using client-side templates
  2. Implement client-side pagination for the items list
  3. Create a "detail view" template that shows when an item is clicked
  4. Add animation effects when items are added or removed
  5. 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! :)