How to Build an Omni-Channel Micro-App with Node.js and Express in 2 Hours

Written by chris-horn-uk | Published 2020/03/18
Tech Story Tags: nodejs | express | web-development | omnichannel | javascript | sms | chatbots | programming

TLDR ONEm is a developer framework and platform that allows developers to rapidly build fully functional omni -channel micro-apps. The ONEm platform lets you host your micro-app anywhere on the Internet. We’ll write a web widget that can be integrated into any website... and the same codebase will also support an SMS client out of the box. The tutorial assumes a basic knowledge of Node.js. The code used in this tutorial can be accessed via the git repository here.via the TL;DR App

I’m the CTO of ONEm Communications Ltd. We’ve built a developer framework and platform that allows developers to rapidly build fully functional omni -channel micro-apps.
In this article, I’ll show you how to build an example micro-app from scratch using our simple Node.js SDK. The tutorial assumes a basic knowledge of Node.js.
We’ll retrieve the movie details courtesy of the free api provided by http://themoviedb.org (please make sure to abide by their terms).
We'll write a web widget that can be integrated into any website...
And the same codebase will also support an SMS client out of the box...
The ONEm platform lets you host your micro-app anywhere on the Internet. For local testing, We’ll use ngrok to provide a publicly accessible URL so that we have a bridge between our app running on localhost and the ONEm Framework.

Pre-requisites

1. Sign up for a free api key at https://www.themoviedb.org/account/signup
2. Sign up for a free ONEm developer account at https://developer-portal.onem.zone/
3. Install ngrok from https://ngrok.com/

Micro-app basics

When users make requests from web a widget or via SMS, the ONEm Framework will invoke an HTTP callback to the micro-app’s web server. The micro-app should respond with HTTP status code 200 and include, in the response body, some JSON which tells the ONEm Framework how to render the response to the user. If you’re intrigued and want to know more, you can checkout the developer docs at https://developer-docs.onem.zone.
Our Node.js SDK provides an abstraction of the JSON format so you don’t have to worry about the details. You have two options, you can use the SDK to produce JSON by passing parameters to the functions provided, or alternatively you can use a templating library and write your micro-app’s views in HTML :) In this tutorial, we’ll be using the second option and specifically pug as the templating language, you can use ejs instead if you prefer.
Ok, enough chat, let’s get coding!

Project Structure and Code

The code used in this tutorial can be accessed via the git repository here
Our project structure will look like this:
/
index.js
package.json
.env
/app_api
/routes
index.js
/views
landing.pug
movieView.pug
/public
/index.html
Install Express and the project’s base dependencies:
 $ npm install express dotenv jwt-simple request-promise url-join
We’ll also use the ONEm Framework’s Node.js SDK:
$ npm install onemsdk
Create an index.js in the project root with the following content:
require('dotenv').config()
const express = require('express')
const api = require('./app_api/routes')

const app = express()
const PORT = process.env.PORT || 3000

app.use(express.json())
app.use(express.static(__dirname + '/public'));
app.use('/api', api)

app.get('/', function (req, res) {
    res.sendFile('/public/index.html', { root: __dirname })
})

app.get('*', function (req, res) {
    res.sendFile('/public/index.html', { root: __dirname })
})

app.get('/*', function (req, res) {
    res.sendFile('/public/index.html', { root: __dirname })
})

app.listen(PORT, () => console.log(`Example micro-app listening on port ${PORT}`))
When users make requests from our micro-app, such as selecting a menu option or submitting a form, the ONEm Framework will issue a HTTP request to the micro-app’s callback path. In the index.js we set /api as the ‘basepath’ for all requests coming from the ONEm Framework with this line:
app.use('/api', api)
When the micro-app is launched for the first time by a user, the ONEm Framework will display the micro-app’s “landing page”. This is a bit like the index.html of a typical web application. The landing page is invoked by the ONEm Framework issuing a request to the micro-app’s API base path, in our case /api.
So we’ll want our app to handle a basic call to GET /api and respond with some initial content that the user will see.
For our example, we’ll create a static menu, so that our users can view three different movies:
When the user clicks on a movie title, we’ll show the movie’s image and description.
First off, let’s create the landing menu options by creating a landing.pug in the /views folder:
section
  header Movie Menu
  ul
    li
        a(href='/movie/76341/') Mad max
    li
        a(href='/movie/419704/') Ad Astra
    li
        a(href='/movie/454626/') Sonic the Hedgehog
And let’s connect the base route in Express in /routes/index.js:
const jwt = require('jwt-simple')
const express = require('express')
const request = require('request-promise')
const urlJoin = require('url-join')
const { loadTemplate } = require('onemsdk').parser
const { Response } = require('onemsdk')

const api = express.Router()

// get this by signing up for an account at https://www.themoviedb.org/
const READ_ACCESS_TOKEN = process.env.READ_ACCESS_TOKEN

if (!READ_ACCESS_TOKEN) throw "themoviedb.org READ_ACCESS_TOKEN not found in environment variables"

const moviedbProps = {
    baseUrl: 'https://api.themoviedb.org',
    baseImagePath: 'http://image.tmdb.org/t/p/w185/'
}

const VIEWS_PATH = './app_api/views/'

const views = {
    VIEW_LANDING: `${VIEWS_PATH}landing.pug`,
    VIEW_MOVIE: `${VIEWS_PATH}movieView.pug`,
}

api.get('/', async (req, res) => {

    try {
        let rootTag = loadTemplate(views.VIEW_LANDING, {})
        let response = Response.fromTag(rootTag)
        res.json(response.toJSON())
    } catch (e) {
        console.log(e)
        res.status(500).json({ success: false, message: 'server error' })
    }
})

api.get('/movie/:id', async (req, res) => {

    let movieId = req.params.id

    try {
        let data = await request(urlJoin(moviedbProps.baseUrl, `/3/movie/${movieId}`), {
            json: true,
            headers: {
                'Authorization': 'Bearer ' + READ_ACCESS_TOKEN
            }
        })

        if (data.poster_path) {
            data.poster_path = urlJoin(moviedbProps.baseImagePath, data.poster_path)
        }

        let rootTag = loadTemplate(views.VIEW_MOVIE, data)
        let response = Response.fromTag(rootTag)
        res.json(response.toJSON())
    } catch (e) {
        console.log(e)
        res.status(500).json({ success: false, message: 'server error' })
    }
})

module.exports = api
/*
* Here we set the a path to the landing.pug file
*/
const views = {
VIEW_LANDING: `${VIEWS_PATH}landing.pug`,
VIEW_MOVIE: `${VIEWS_PATH}movieView.pug`
}
/*
* The api's base path is called when users hit our app
* for the first time. We'll use the onemsdk to parse the
* pug file and generate the raw JSON response
*/
api.get('/', async (req, res) => {
    try
 {
let rootTag = loadTemplate(views.VIEW_LANDING, {})
let response = Response.fromTag(rootTag)
res.json(response.toJSON())
}
catch
 (e) {
console.log(e)
res.status(500).json({ success: false, message: 'server error' })
}
})
/routes/index.js also contains a route for the movie view page, when the user clicks on a movie title, the ONEm Framework will issue an HTTP GET callback to the href associated with that option, eg in landing.pug we have the following line:
a(href='/movie/76341/') Mad max
Later in this tutorial, we will tell the ONEm Framework to use the base path of /api, which will eventually translate into HTTP GET /api/movie/76341 when the user selects to view Mad Max.
Ok, so a quick recap. We have created two views using pug. Our web server is ready to accept callbacks from /api and /api/movie/{:id}
So what’s remaining now is to complete the setup of our micro-app.
Create a .env file in the project root path:
PORT=3000
READ_ACCESS_TOKEN=<themoviedb API Read Access Token (v4 auth)
2. We’ll use ngrok to give us a publicly accessible url to our Micro-app at localhost:3000.
ngrok http 3000
Copy the link provided by ngrok, it will be something like https://6f1ca0d4.ngrok.io
3. In the ONEm Developer Portal, select “Create App” and set the callback path to your app appending /api to the base path, eg: https://6f1ca0d4.ngrok.io/api
4. In the ONEm Developer Portal, select your new app and then select “Web channel”. Copy/paste the javascript code shown in the tab, into the body section of the /public/index.html of your Micro-app:
...
<body>
<h1>Movie micro-app</h1>
<script src="https://bot.onem.zone/bundle.js"></script>
<script>
ONEmStart({ app_id: "5eb948e62d60d1001f32fb83" }).render('body');
</script>
</body>
Tip: This code snippet can be included in any website, if you have access to another website’s index.html, then go ahead and try it.

Running

Fire up your micro-app:
$ node index
In your browser, visit localhost:3000 (or where ever your app is configured to listen).
The ONEm Micro-app should be visible in bottom right-hand corner. Click the icon to open.
You can also view the SMS client from the developer portal test client. After registering your mobile number, you can test out the SMS interface by entering # followed by your micro-app name in the input box.
This is of course just an introduction, if this has intrigued you to learn more then you can access more details at https://developer-docs.onem.zone

Written by chris-horn-uk | Chief Product Builder and CTO of IDZ LTD
Published by HackerNoon on 2020/03/18