Add a headless CMS to Ember in 5 minutes

Contents

In this short tutorial we show you how to integrate the Storyblok API into an Ember App. We build the components step by step and develop the integration using Storyblok API and Storyblok SDK. This is the final result:

You can clone this tutorial at https://github.com/storyblok/storyblok-ember-boilerplate

Environment Setup

Requirements

  • Understanding of Ember

  • Node, yarn (or npm) and npx installed

  • An account on Storyblok to manage content

  • A space already configured in Storyblok

Setup the project

We use ember-cli to generate the initial setup for our project and to generate the routes, components and other things. To generate the initial project, execute the new command:

# generate the project
ember new storyblok-test

# install ember-fecth addon
ember install ember-fetch

This command will config the Ember application in storyblok-test folder and install ember-fecth package.

The next step is to load all of the data from Storyblok API into your Ember application. For this, it is necessary to create a index route:

ember g route index

After this, edit the app/router.js file to map the index route as a dynamic route with path param like this:

// route.js
Router.map(function() {
  this.route('index', { path: '/:path' })
});

The index route will receive a path as a parameter. This is useful when we get data from the Storyblok API

Creating a service to connect the Storyblok API

Start by creating a service to put in a Storyblok API connection logic. Execute the generate service command:

ember g service storyblok

This command creates a storyblok.js file into the app/services folder. Then edit it:

import Service from '@ember/service';
import fetch from 'ember-fetch/ajax';

export default Service.extend({
  init() {
    this._super(...arguments);

    const token = '<YOUR_SPACE_TOKEN>'

    this.config = {
      token,
      version: 'draft'
    }
  },

  loadStory(path) {
    const config = this.get('config')

    const URL = `https://api.storyblok.com/v1/cdn/stories/${path}?token=${config.token}&version=${config.version}`

    return fetch(URL)
      .then(response => response.story)
      .catch(err => {
        console.log(err) // eslint-disable-line
      });
  },
});

With the service created, edit the app/routes/index.js file to get this path parameter and use it to load data from Storyblok API:

import Route from '@ember/routing/route';
import { inject as service } from '@ember/service';

export default Route.extend({
  storyblok: service('storyblok'),

  model(params) {
    const { path } = params

    return this.storyblok.loadStory(path)
  }
});

The model function is executed before the route loads and puts the content in the route template. In that case, it will load a specific story from Storyblok API and put it in the template.

Checking what we've done so far

To know if the data is loading correctly, execute the Ember serve command on the terminal using npm or yarn:

yarn start # npm start

Open your browser in http://localhost:4200/home and you should see the Ember welcome page. Modify this to show the page name.

Edit the app/templates/application.hbs by removing all content less than {{outlet}} expression. After that, edit the index.hbs file to show the page name in the h1 tag.

{{!-- application.hbs file --}}
<h1>{{this.model.name}}</h1>

Open your browser again in http://localhost:4200/home. You should see the 'Home' name like this:

Setup the Storyblok components

Now create some components to render the home page correctly.

Teaser component

First, create the component files by using the Ember generate command:

ember g component teaser

Then, edit the teaser.hbs file to this:

<div class="teaser">
  {{this.blok.headline }}
</div>

Grid component

Repeat what was done with the teaser component: execute the generate command and edit the grid.hbs file:

ember g component grid

Edit the grid component using the component helper to render the correct component that came from the Storyblok API

<div class="grid">
  {{#each this.blok.columns as |data|}}
    {{component data.component blok=data}}
  {{/each}}
</div>

Feature component

The last component (for now) is the feature component. Execute in your terminal:

ember g component feature

Edit the feature.hbs file:

<div class="column feature">
  {{this.blok.name}}
</div>

Editing the index.hbs route

Finally, edit the index.hbs file to iterate over our components and render them correctly:

<div class="application">
  {{#each this.model.content.body as |blok|}}
    {{component blok.component blok=blok}}
  {{/each}}
</div>

Adding some styles

Before seeing what it looks like, try adding some styles. Add this CSS link in the app/index.html file and edit the app/styles/app.css to it:

* {
  box-sizing: border-box;
}

.application {
  max-width: 760px;
  margin: 0 auto;
  padding-left: 20px;
  padding-right: 20px;
}

.grid .ember-view {
  flex: 1;
}

.grid .ember-view .feature {
  width: auto;
  text-align: center;
}

Open your browser again in http://localhost:4200/home:

About page

Create a new page called 'About' in our Storyblok space. Then add two components on schema: a Teaser component and a Markdown component. The last one will be a Richtext component with a simple Lorem Ipsum text.

Creating a Navbar component

Before setting up the About page in this project, create a Navbar component to switch between pages. Execute this command in your terminal:

ember g component navbar

Edit the navbar.hbs file like this:

<ul class="navbar">
  <li class="navbar__item">
    <a href="/home"> Home </a>
  </li>

  <li class="navbar__item">
    <a href="/about"> About </a>
  </li>
</ul>

Add this component in the index.hbs file:

<div class="application">
  <Navbar />

  {{#each this.model.content.body as |blok|}}
    {{component blok.component blok=blok}}
  {{/each}}
</div>

And lastly, add some styles in app.css replacing the content with the following:

* {
  box-sizing: border-box;
}

ul {
  margin: 0;
  padding: 0;
}

li {
  list-style: none;
}

.application {
  max-width: 760px;
  margin: 0 auto;
  padding-left: 20px;
  padding-right: 20px;
}

.grid .ember-view {
  flex: 1;
}

.grid .ember-view .feature {
  width: auto;
  text-align: center;
}

.application .navbar {
  display: flex;
  justify-content: flex-end;
  margin: 20px 0;
}

.application .navbar .navbar__item:not(:first-of-type) {
  margin-left: 10px;
}

Now open your browser and check if the Home page looks like this:

Go to the About page by clicking the About link in navbar... What do you see? Hopefully you see the Navbar links and an About title. But where is the Loren Ipsum text from Markdown? If you open the console's browser there will be an error message saying that the 'markdown' component name does not exist. Let's fix this!

Set up the Richtext component

You can find more information about the Rich Text component and how to you use it in our documentation. For now, use our Javascript SDK with the resolver to transform the complex JSON structure from Storyblok into HTML that we will use for our page.

Execute the generate component command in our terminal:

ember g component markdown

Install the Storyblok Javascript SDK:

yarn add storyblok-js-client # npm install storyblok-js-client

Edit the markdown.js file creating an Ember file that includes the transformations that we need.

import Component from '@ember/component';
import { computed } from '@ember/object';
import { inject as service } from '@ember/service';

export default Component.extend({
  storyblok: service('storyblok'),

  htmlData: computed('blok', function() {
    const blok = this.get('blok');
    return this.storyblok.richtextResolver(blok.content);
  })
});

Finally, edit the markdown.hbs file to show that the HTML has been transformed.

{{{this.htmlData}}}

You see that we use the Storyblok service that we created before. Now, replace the storyblok.js file with the following content:

import Service from '@ember/service';
import fetch from 'ember-fetch/ajax';
import StoryblokClient from 'storyblok-js-client';

export default Service.extend({
  init() {
    this._super(...arguments);

    const token = '<YOUR_SPACE_TOKEN>'

    this.config = {
      token,
      version: 'draft'
    }

   // initialize the SDK
    this.client = new StoryblokClient({
      accessToken: token
    })
  },

  loadStory(path) {
    const config = this.get('config')

    const URL = `https://api.storyblok.com/v1/cdn/stories/${path}?token=${config.token}&version=${config.version}`

    return fetch(URL)
      .then(response => response.story)
      .catch(err => {
        console.log(err) // eslint-disable-line
      });
  },

  // we add this function to use the resolver function from SDK
  richtextResolver(content) {
    return this.client.richTextResolver.render(content)
  }
});

Open your browser in http://localhost:4200/about and see the correct content:

Conclusion

In this tutorial you learned how to integrate Storyblok API in an Ember framework. You created some components and used the Ember Service to integrate the Storyblok data into an Index route. You can clone this tutorial at https://github.com/storyblok/storyblok-ember-boilerplate too.

Ademar Cardoso

Author

Ademar Cardoso

Working as a front-end developer at Storyblok, passionate about new technologies and the open source community.