How to cache compiled templates of Haml-js in Node.js
I’ve recently been diving into Node.js, and I’ve been really impressed by how its event-driven, server-side architecture makes it so easy to build fast, scalable apps. As I started exploring different template engines to pair with it, I landed on Haml-js. Together, they form a fantastic stack for keeping frontend markup clean and highly maintainable.
However, template compilation can become a hassle as traffic grows. To get the best possible performance out of Haml-js, it’s a smart move to cache your compiled template functions in memory rather than reading files and compiling them on every single request.
I decided to use our same Memcached to handle this, and in this post, I’ll walk you through how I set it up.
(Note: Assuming you already have a basic understanding of Node.js and have both Node and Haml-js installed on your machine. You will also need a Memcached server running locally. You can follow steps at https://nodejs.org/ and https://howtonode.org/haml-for-javascript for installation steps )
Project Setup and Template Creation
First, let’s create a fresh directory for our project files:
mkdir nodework
cd nodeworkInside this folder, create a sample template file named sample.haml with some basic markup:
!!! Strict
%html(lang="en")
%head
%title= title
%body
= contentsSetting Up the Dependencies and Server
Next, let’s create our main application file, mem.js. We’ll start by loading our required modules, connecting to our local Memcached instance, and setting up some dummy data to feed into the template.
var Haml = require('haml'),
fs = require('fs'),
Memcached = require('memcached');
// Connect to your local Memcached instance (default port is 11211)
var memcached = new Memcached('127.0.0.1:11211');
// Dynamic data to inject into our Haml view
var data = {
title: 'Hello World',
contents: 'Hello World! This page is rendered using cached Haml-js templates.'
};If we were to build a basic HTTP server that compiles and renders this file on every page load, the server setup would look like this:
var http = require('http');
http.createServer(function (req, res) {
res.writeHead(200, {'Content-Type': 'text/html'});
// Reading and compiling on every request (Not ideal for production!)
var haml = fs.readFileSync('sample.haml', 'utf8');
var js = Haml.compile(haml);
res.end(Haml.execute(js, null, data));
}).listen(1337, '127.0.0.1');
console.log('Server running at http://127.0.0.1:1337/');While this works, calling fs.readFileSync and Haml.compile() on every hit is going to slow things down under heavy load.
Implementing the Memcached Cache Layer
To fix this, we’ll alter the logic inside our server loop. Before touching the file system, we will ask Memcached if it already has the compiled template stored under the key 'compiled'.
- If it exists, we grab it and render it immediately.
- If it doesn’t exist (a cache miss), we read the file, compile it, and save it to Memcached so it’s ready for the next visitor.
Here is the complete implementation:
var http = require('http');
http.createServer(function (req, res) {
res.writeHead(200, {'Content-Type': 'text/html'});
// 1. Check Memcached for the pre-compiled template function
memcached.get('compiled', function (err, result) {
if (err) {
console.error('Memcached get error:', err);
}
var js = result;
// 2. If cache is empty, read, compile, and store it
if (!js) {
console.log('Cache miss! Compiling template...');
var haml = fs.readFileSync('sample.haml', 'utf8');
js = Haml.compile(haml);
// Store the compiled template for 1000 seconds
memcached.set('compiled', js, 1000, function (err) {
if (err) {
console.error('Memcached set error:', err);
}
});
} else {
console.log('Cache hit! Serving template from memory.');
}
// 3. Execute the compiled Haml function with our data and return it
res.end(Haml.execute(js, null, data));
});
}).listen(1337, '127.0.0.1');
console.log('Server running at http://127.0.0.1:1337/');Conclusion
Now, the very first time you hit http://127.0.0.1:1337/, Node will read your sample.haml file from the disk and compile it. On every single refresh after that, it bypasses the disk completely and pulls the compiled code directly from memory—giving your application a massive performance boost.
Are you using a different caching strategy or wrapper for your template engines in Node? Let me know your thoughts or suggestions in the comments below!