Almost EVERYONE who tried headless systems said they saw benefits. Download the state of CMS now!

Storyblok now on AWS Marketplace: Read more

O’Reilly Report: Decoupled Applications and Composable Web Architectures - Download Now

Empower your teams & get a 582% ROI: See Storyblok's CMS in action

Skip to main content

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

Try Storyblok

Storyblok is the first headless CMS that works for developers & marketers alike.

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

On May 13th, 2024, Storyblok started gradually rolling out a new design for its Visual Editor. Therefore, the Visual Editor product screenshots depicted in this resource may not match what you encounter in the Storyblok App. For more information and a detailed reference, please consult this FAQ on the new Visual Editor design.

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

Be sure that you have Node.js installed (version > 4). Check your version using npm -v in the terminal.

Section titled Getting started 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

Section titled Install express and a template engine 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

Section titled Create an express server 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!');

Section titled Add the API-client of Storyblok Add the API-client of Storyblok

Let's install the headless CMS client to our Node.js app.

      npm install storyblok-js-client --save

Section titled Configure a route and initialize the client 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;

    .get(`cdn/stories/${path}`, {
      version: req.query._storyblok ? 'draft': 'published'
    .then((response) => {
      res.render('index', {
    .catch((error) => {

Section titled Add the templates Add the templates

Create the file main.hbs in the folder views/layouts with following content.

      <!DOCTYPE html>
<html lang="en">
  <meta charset="utf-8">
  <title>{{ }} - Storyblok</title>
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <link rel="stylesheet" href="" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">

  <script type="text/javascript" src="//"></script>
  <script type="text/javascript">

    // On the change event refresh the window
    storyblok.on('change', function() {

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') }}

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>

The file structure of your project should now look like the following screenshot.

Headless cms Node.js Screenshot

Section titled Ready! Start the server 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

Now that you have successfully integrated Storyblok in your project let's create a "Story" in your own Storyblok space.

Section titled Using the CLI: Using the CLI:

  1. npm install -g storyblok
  2. storyblok quickstart

Section titled Using the Webinterface: Using the Webinterface:

  1. Go to!/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.

Section titled Exchange the preview token 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'

Section titled Add your environment 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.

Section titled Conclusion Conclusion

As you can see, with this concept, you can create very flexible layouts. As inspiration we made a screenshot of where you can see a layout with deeply nested components.

Headless cms Node.js Screenshot

Section titled Advanced setup: Caching 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
// For now we will use a memory cache
let Storyblok = new StoryblokClient({
  accessToken: 'YOUR_TOKEN',
  cache: {
    type: 'memory'

Section titled How to clear the cache? 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) {
  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=""></script>
<script type="text/javascript" src="//"></script>
<script type="text/javascript">

  // On the change event refresh the window
  storyblok.on('change', function() {

  // Listen on the published event to clear the cache
  storyblok.on('published', function() {
      url: '/clear_cache'
    .done(function() {
      console.log('cache cleared!');
    .fail(function() {
      console.log('error clearing cache');

I hope you enjoyed this tutorial and I would be happy to receive feedback.


Alexander Feiglstorfer

Alexander Feiglstorfer

Passionate developer and always in search of the most effective way to resolve a problem. After working 13 years for agencies and SaaS companies using almost every CMS out there he founded Storyblok to solve the problem of being forced to a technology, proprietary template languages and the plugin hell of monolithic systems.