How to Create a Custom App for Storyblok with Nuxt.js and Oauth2

Contents

In this guide we explain how to create a custom application in the context of a Storyblok app using the Nuxt.js framework. Together, we will create a simple board that shows the status of stories in our space. Storyblok users can reach this board from the left action panel.

Stories Board created with Nuxt.js for Storyblok

Requirements

Setup

First, you need to create a new app in Storyblok and also create a single page application with Nuxt.js.

Creating an App in Storyblok

If you haven’t already, you must join the Storyblok partner program (click here for more info). You should see Apps in the left action panel. Open the apps section and create a new app. You will land on the following screen where you can select Sidebar as the type of the app.

For now, fill only the name and slug of your application.

Create new app in Storblok screen

Create Nuxt.js App

Create a new Nuxt.js project using the create-nuxt-app package and choose one of the following setups:

  • SPA mode
  • Include TailwindCSS
  • Include dotenv

Test your Nuxt.js application and make sure it is all working correctly.

Connect Nuxt.js App with Storyblok App

To connect and authenticate your Nuxt.js app with Storyblok, install the @storyblok/nuxt-auth module into Nuxt.js.

yarn add @storyblok/nuxt-auth

If you checked the documentation of the @storyblok/nuxt-auth, you know we have to provide 3 environment variables. We highly recommend providing them using the dotenv file.

The modules array in nuxt.config.js should look like this. Don’t forget to require the dotenv module require('dotenv').config() at the top.

modules: [
    // Doc: https://axios.nuxtjs.org/usage
    '@nuxtjs/axios',
    // Doc: https://github.com/nuxt-community/dotenv-module
    '@nuxtjs/dotenv',
    [
      '@storyblok/nuxt-auth',
      {
        id: process.env.CONFIDENTIAL_CLIENT_ID,
        secret: process.env.CONFIDENTIAL_CLIENT_SECRET,
        redirect_uri: process.env.CONFIDENTIAL_CLIENT_REDIRECT_URI,
        debug: true
      }
    ]
  ],

Your .env file should look like this.

BASE_URL=http://localhost:3000
CONFIDENTIAL_CLIENT_ID="copy from Storyblok"
CONFIDENTIAL_CLIENT_SECRET="copy from Storyblok"
CONFIDENTIAL_CLIENT_REDIRECT_URI="{ngrok url}/auth/callback"

Now fill inCONFIDENTIAL_CLIENT_ID and CONFIDENTIAL_CLIENT_SECRET. To get these values, go to the Apps section of the partner portal and open up your recently created app. Copy Client id and Secret values and paste them into the .env file for CONFIDENTIAL_CLIENT_ID and CONFIDENTIAL_CLIENT_SECRET.

Detail screen of the app in Storyblok

Create NGROK Tunnel for Development

We are still missing the CONFIDENTIAL_CLIENT_REDIRECT_URI, which we will get using the NGROK service. If you are unfamiliar with this service, it creates a tunnel to your localhost and provides public URLs, which we can use in Storyblok.

Download and setup NGROK following their documentation. Then start your Nuxt.js application and create a tunnel using NGROK.

yarn dev // starts nuxt in dev mode on localhost:3000

In the root folder start the NGROK service.

ngrok http 3000

If NGROK starts successfully you should output like this.

ngrok by @inconshreveable                                                                  (Ctrl+C to quit)
                                                                                                           
Session Status                online                                                                       
Account                       Samuel Snopko (Plan: Free)                                                   
Version                       2.3.35                                                                       
Region                        United States (us)                                                           
Web Interface                 http://127.0.0.1:4040                                                        
Forwarding                    http://30ff0773.ngrok.io -> http://localhost:3000                            
Forwarding                    https://30ff0773.ngrok.io -> http://localhost:3000                                  

You can see that https://30ff0773.ngrok.io points to http://localhost:3000. Your URL will look different, NGROK creates new URLs every time.

Paste the NGROK URL into CONFIDENTIAL_CLIENT_REDIRECT_URI according to the pattern: {ngrok https url}/auth/callback. Your .env file should look like this:

BASE_URL=http://localhost:3000
CONFIDENTIAL_CLIENT_ID="asdfgqwers=="
CONFIDENTIAL_CLIENT_SECRET="1fsfasgasgagvaf14fdfgas4fgPYRx24gFQtPw=="
CONFIDENTIAL_CLIENT_REDIRECT_URI="https://30ff0773.ngrok.io/auth/callback"

Show Your App in the Storyblok Sidebar

We want to show our Nuxt.js project as a sidebar app in Storyblok. In the app settings screen, fill in Url to your app and Oauth2 callback URL of the live environment. Just follow this pattern {ngrok https url}/auth/connect/storyblok and {ngrok https url}/auth/callback and fill in the fields. If you have an SVG icon right now, you can provide it, but it is not necessary for this example.

How to show app in sidebar

IMPORTANT

If you restart the NGROK service, you must refresh all NGROK URLs in .env file and in the app settings in Storyblok.

Install Custom App in Storyblok Space

You can install your app like any other app in one of your spaces in Storyblok. Open the App Directory in your space and change the filter to show your custom apps. Open the details of your app and hit the install button.

How to install custom app

Create a Simple App

At this point you should have the Nuxt.js app and the NGROK tunnel running and connected to the Storyblok app. Next we need to get some data and create a simple board. Replace your pages/index.vue with the following code:

<template>
  <div>
    <!-- <div>
      {{ stories }}
    </div> -->
    <div class="flex justify-center">
      <div class="min-h-screen flex overflow-x-scroll py-12">
        <div
          v-for="column in columns"
          :key="column.title"
          class="bg-gray-100 rounded-lg px-3 py-3 column-width rounded mr-4"
        >
          <p class="text-gray-700 font-semibold font-sans tracking-wide text-sm">{{column.title}}</p>
          <story-card
            v-for="(story) in column.stories"
            :key="story.id"
            :story="story"
            class="mt-3"
          />
        </div>
      </div>
    </div>
  </div>
</template>

<script>
import StoryCard from "~/components/StoryCard.vue";
import axios from 'axios'

export default {
  components: {
    StoryCard
  },
  data() {
    return {
      story: {},
      stories: [],
      columns: [
        {
          title: "Unpublished",
          stories: []
        },
        {
          title: "Unpublished Content",
          stories: []
        },
        {
          title: "Published",
          stories: []
        }
      ]
    }
  },
  mounted() {
    if (window.top == window.self) {
      window.location.assign('https://app.storyblok.com/oauth/app_redirect')
    } else {
      this.loadStories()
    }
  },
  methods: {
    loadStories() {
      axios.get(`/auth/explore/spaces/${this.$route.query.space_id}/stories`)
        .then((res) => {
          this.stories = res.data.stories

          for (const story of res.data.stories) {
            const compStory = {
              title: story.name,
              published: story.published,
              unpublished_changes: story.unpublished_changes,
              id: story.id
            }
            if (compStory.published) {
              this.columns[0].stories.push(compStory)
            } else if (compStory.unpublished_changes) {
              this.columns[1].stories.push(compStory)
            } else {
              this.columns[2].stories.push(compStory)
            }
          }
        })
    },
    loadStory() {
      axios.get(`/auth/explore/spaces/${this.$route.query.space_id}/stories/7038709`)
        .then((res) => {
          this.story = res.data.story
        })
    }
  }
}
</script>

<style>
.container {
  margin: 0 auto;
  min-height: 100vh;
  display: flex;
  justify-content: center;
  align-items: center;
  text-align: center;
}

.title {
  font-family: 'Quicksand', 'Source Sans Pro', -apple-system, BlinkMacSystemFont,
    'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
  display: block;
  font-weight: 300;
  font-size: 100px;
  color: #35495e;
  letter-spacing: 1px;
}

.subtitle {
  font-weight: 300;
  font-size: 42px;
  color: #526488;
  word-spacing: 5px;
  padding-bottom: 15px;
}

.links {
  padding-top: 15px;
}
</style>

Create a components/StoryCard.vue file:

<template>
  <div class="bg-white shadow rounded px-3 pt-3 pb-5 border border-white" style="min-width: 300px;">
    <div class="flex justify-between">
      <p class="text-gray-700 font-semibold font-sans tracking-wide text-sm">{{story.title}}</p>
    </div>
    <div class="flex mt-4 justify-between items-center">
      <span class="text-sm text-gray-600">[{{story.id}}]</span>
    </div>
  </div>
</template>

<script>
export default {
  props: {
    story: {
      type: Object,
      default: () => ({})
    }
  }
};
</script>

Then navigate to the space where you installed your app. If you click on it in the sidebar you will be redirected to the authentification screen.

Authentification screen

At first it will take some time to load your app through the NGROK tunnel, but future changes in Nuxt.js will be much faster. Finally, you should see a screen similar to this one. If you didn’t previously create any other content, you will see only one story Home on the board. You can do it now!

App with Nuxt.js and Storyblok

Conclusion

This guide showcased an example of creating custom apps in Storyblok. Using these techniques you can create your own apps and host them for authenticated users using technologies you’re most comfortable with. This opens the door for you to create custom apps and features to solve your users’ problems. Using our Management API you are better able to handle the content of your space.

Next step

If you are interesting in creating your own custom Nuxt.js app, you should check the Storyblok’s Nuxt-Auth module out and try out our Workflow App example (fully draggable board) built with this technology stack.

TopicLink
Basic documentation of the authenticationhttps://www.storyblok.com/docs/authentication-apps
Storyblok Nuxt-Auth Modulehttps://www.npmjs.com/package/@storyblok/nuxt-auth
Repo of the Workflow App Examplehttps://github.com/storyblok/storyblok-workflow-app
Partner Progam Informationhttps://www.storyblok.com/partners

About the author

Samuel Snopko

Samuel Snopko

Samuel is the Head of Developer Relations at Storyblok with a passion for JAMStack and beautiful web. Co-creator of DX Meetup Basel and co-organizer of Frontend Meetup Freiburg. Recently fell in love with Vue.js, Nuxt.js, and Storyblok.


More to read...