Add a headless CMS to Node.js using Express as server.
Storyblok is the first headless CMS that works for developers & marketers alike.
In this article I will show you how to integrate Storyblok, a component composer and headless CMS, to your Express.js app in a few easy steps. You can also download the code of this tutorial at github.com/storyblok/storyblok-express-boilerplate
Prerequsites
Be sure that you have Node.js installed (version > 4). Check your version using npm -v
in the terminal.
Getting started
First of all we need to create the project directory and initialize the project with npm init
to create a package.json.
mkdir express-example
cd express-example
npm init
Install express and a template engine
We will use express as a server and install handlebars as a template engine. You can choose your template engine of your preference like Pug, Haml or EJS.
npm install express --save
npm install express-handlebars --save
Create an express server
Create the file index.js
the root of your project folder and insert following code to setup the express server.
'use strict';
const express = require('express');
const exphbs = require('express-handlebars');
const url = require('url');
const app = express();
app.use('/public', express.static('public'));
// Define your favorite template engine here
app.engine('.hbs', exphbs({
defaultLayout: 'main',
extname: '.hbs',
partialsDir: 'views/components/'
}));
app.set('view engine', '.hbs');
app.set('views', 'views')
app.listen(4300, function() {
console.log('Example app listening on port 4300!');
});
Add the API-client of Storyblok
Let's install the headless CMS client to our Node.js app.
npm install storyblok-js-client --save
Configure a route and initialize the client
Add following code in your index.js
right after const app = express();
// ...
app = express();
// 1. Require the Storyblok JS client
const StoryblokClient = require('storyblok-js-client');
// 2. Initialize the client
// You can use this preview token for now, we'll change it later
let Storyblok = new StoryblokClient({
accessToken: 'J0irYFbngEQ6ZFlRqs6llwtt'
});
// 3. Define a wilcard route to get the story mathing the url path
app.get('/*', function(req, res) {
var path = url.parse(req.url).pathname;
path = path == '/' ? 'home' : path;
Storyblok
.get(`cdn/stories/${path}`, {
version: req.query._storyblok ? 'draft': 'published'
})
.then((response) => {
res.render('index', {
story: response.data.story
});
})
.catch((error) => {
res.send(error);
});
});
Add the templates
Create the file main.hbs
in the folder views/layouts
with following content.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>{{ story.name }} - Storyblok</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
</head>
<body>
{{{body}}}
<script type="text/javascript" src="//app.storyblok.com/f/storyblok-latest.js"></script>
<script type="text/javascript">
storyblok.init();
// On the change event refresh the window
storyblok.on('change', function() {
window.location.reload(true);
});
</script>
</body>
</html>
Now we need to create the file index.hbs
in the folder views
.
{{{ story.content._editable }}}
<div class="container">
{{#each story.content.body}}
{{> (lookup . 'component') }}
{{/each}}
</div>
Let's create a teaser component inside the folder views/components
called teaser.hbs
. As you can see there is a variable with the name _edtiable
at the beginning of the component. This variable by default is empty and will output a html comment when you are receiving the draft version of the story from the api. This html comment tells Storyblok which element is clickable for showing in the side-by-side editor.
{{{ _editable }}}
<div class="jumbotron">
<h1>{{ title }}</h1>
<p>{{ description }}</p>
</div>
The file structure of your project should now look like the following screenshot.
Ready! Start the server
Start the express server by running following command.
node ./index.js
If you open your browser and go to localhost:4300 you should now see your teaser component with some demo data.
Now that you have successfully integrated Storyblok in your project let's create a "Story" in your own Storyblok space.
Using the CLI:
npm install -g storyblok
storyblok quickstart
Using the Webinterface:
- Go to https://app.storyblok.com/#!/signup and do the signup
- Create a new Space.
Both ways will start with the quickstart which will guide you through your first time creating a component in Storyblok.
Exchange the preview token
After the space has been created and you've followed the teaser creation of the quickstart -
copy your preview access token from the dashboard of your space and replace it in index.js
.
let Storyblok = new StoryblokClient({
accessToken: 'YOUR_TOKEN_HERE'
});
Add your environment
After adding your own token from your Space to your project - we will have to also tell Storyblok where to find our dev environment. For this we will navigate to the Settings of a Space and add the URL http://localhost:4300/
as a new Preview url.
You can now add another key (description) to the schema of the teaser component and in the end the teaser component should look like in the following screenshot. All keys and the component name are "humanized" so that you as a developer can use the technical small letter value while the editor get's a human readable value.
Conclusion
As you can see, with this concept, you can create very flexible layouts. As inspiration we made a screenshot of storyblok.com where you can see a layout with deeply nested components.
Advanced setup: Caching
You may want to cache the api request that the Storyblok client does when using this app in production. Thankfully the client already has cache providers built in. To activate the cache add the cache option to the client initialization.
// You can find other cache provider options
// on https://github.com/storyblok/storyblok-js-client
// For now we will use a memory cache
let Storyblok = new StoryblokClient({
accessToken: 'YOUR_TOKEN',
cache: {
type: 'memory'
}
});
How to clear the cache?
In order to clear the cache we can listen to the 'published' events of the Storyblok script and call the endpoint /clear_cache. Let's add a route in index.js
right before the wildcard route.
// Define a clear cache route for the publishing hook.
app.get('/clear_cache', function(req, res) {
Storyblok.flushCache();
res.send('Cache flushed!');
});
Next we will need to add the listener to the template-file views/layouts/main.hbs
. We will use the jQuery ajax function to call the route but you can also write the request in plain js or use another ajax libary.
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.2.4/jquery.min.js"></script>
<script type="text/javascript" src="//app.storyblok.com/f/storyblok-latest.js"></script>
<script type="text/javascript">
storyblok.init();
// On the change event refresh the window
storyblok.on('change', function() {
window.location.reload(true);
});
// Listen on the published event to clear the cache
storyblok.on('published', function() {
$.ajax({
url: '/clear_cache'
})
.done(function() {
console.log('cache cleared!');
})
.fail(function() {
console.log('error clearing cache');
});
});
</script>
I hope you enjoyed this tutorial and I would be happy to receive feedback.