Add a headless CMS to Node.js using Express as server.

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-node-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 node client
const StoryblokClient = require('storyblok-node-client');

// 2. Initialize the client
// You can use this private token for now, we'll change it later
let Storyblok = new StoryblokClient({
  privateToken: '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(`stories/${path}`, {
      version: req.query._storyblok ? 'draft': 'published'
    })
    .then((response) => {
      res.render('index', {
        story: response.body.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.

Headless cms Node.js 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.

Headless cms Node.js Screenshot

Configuring Storyblok

Now that you have successfully integrated Storyblok in your project let’s create a “Story” in your own Storyblok space.

Using the CLI:

  1. npm install -g storyblok
  2. storyblok quickstart

Using the Webinterface:

  1. Go to https://app.storyblok.com/#!/signup and do the signup
  2. Create a new Space.

Both ways will start with the quickstart which will guide you through your first time creating a component in Storyblok.

follow the quickstart

Exchange the private token

After the space has been created and you’ve followed the teaser creation of the quickstart - copy your private access token from the dashboard of your space and replace it in index.js.

let Storyblok = new StoryblokClient({
  privateToken: 'YOUR_TOKEN_HERE'
});
Your own private token

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 environment.

add a dev environment

The presentation layer for your component

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.

Headless cms Node.js Screenshot

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.

Headless cms Node.js Screenshot

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-node-client
// For now we will use a memory cache
let Storyblok = new StoryblokClient({
  privateToken: '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.


More to read...

About the author

Alexander Feiglstorfer

Passionate developer and always in search of the most effective way to resolve a problem. Thinking in components helped him to be five times more productive. After working for big agencies and developing a scalable e-commerce platform he founded Storyblok.