Build a Serverless MERN Story App With Webtask.io -- Zero to Deploy: 1

Chris Nwamba

Being a fullstack developer is fun to me and a lot of us enjoy the role because it's more challenging. We have the opportunity as fullstack developers to play with every possible tool on the web and make something reasonable out of it.

Unfortunately some engineers are not fortunate enough to be in an environment that allow them showcase their fullstack powers. Either they are spitting Laravel blade template and rolling out APIs as backend developers or struggling with CSS Flexbox and React as frontend developers.

The pressure is more on frontend engineers because backend engineers can afford to have a good UI without being frontend masters. Frontend engineers just need to know a lot about their server language and even the server configuration to survive.

Serverless technology is starting the put back smiles on our faces as frontend engineers. Now we can focus on the browser and rollout servers within 3 minutes.

Our Task

The image above shows what we intend to build. It's a full functional app written in React and backed by Node, Express and Mongo DB. The backend is "Serverless" with Webtask and composed of just functions and simple Express routes.

Serverless Concept

"Server-less" is a coined term that refers to building web apps without bothering about how the server is set up. The term causes confusion to developers that are new to the concept. It doesn't mean that your app won't live on a server, rather it means that the sever setup and management is left out to be managed by the provisioning platform.

One other thing you might hear when "server-less" is discussed is Function as a Service (FaaS). Serverless technique is simplified by functions. As a developer, you end up writing and deploying compassable functions. This concept will become clearer when we start getting our hands dirty.

Webtask

Google Cloud Functions, stdlib, Azure Cloud Functions, AWS Lambda, etc are all serverless platforms you can lay your hands. The smart guys at Auth0 introduced Webtask which is yet another serverless platform which is seamlessly easy to get started with.

Webtask is used internal by the Auth0 team to manage and run their platform user custom codes. They were generous enough to make this available to the public so we can go ahead and build apps with it. My choice for Webtask is greatly influenced my how easy it is to get started with less overwhelming docs.

Setting Up Project

Our project is split into two -- the webtask backend API and a React driven frontend. Let's take care of Webtast setup first, then setup the frontend when it's time to build that.

Webtask just as you might have guessed, provides a CLI tool to make deploying you functions easy. First thing to do is install this CLI tool:

npm install -g wt-cli

To deploy functions, Webtask needs a way to identify you and your functions. Therefore, an account is needed. Head straight to the Webtask website and login. You will use your email to login to the CLI.

Run the following command to login via your CLI:

wt init <YOUR-EMAIL>

Create your project directory anywhere on your machine, add an index.jsin the directory with the following content:

module.exports = function (cb) {
  cb(null, 'Hello World');
}

Hit the URL logged in the console after running the following command on the directory to see your deployed app:

wt create index

I was amazed too!

Now that we have seen a basic example, let's build something serious and interesting -- an API for our Stories app.

API Project Structure

|---middlwares
|------db.js
|---models
|------Story.js
|---routes
|------stories.js
|---package.json
|---index.js

The pacakge.json contains the dependencies for this project as well as important scripts to run and bundle our project to be Webtask-ready:

{
  "name": "wt-mern-api",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "start": "npm run create -- --watch",
    "create": "wt create index --bundle"
  },
  "author": "",
  "license": "MIT",
  "dependencies": {
    "body-parser": "^1.17.1",
    "express": "^4.15.2",
    "mongoose": "^4.9.5",
    "webtask-tools": "^3.2.0"
  }
}

Webtask Programming Model & Express

Webtask integrates best JavaScript. As you have seen, we just exported a function and an app was created. You might be wondering how you could bring in a different programming model to the scene.

Exporting functions is just one of the programming models supported by Webtask. It happens to be the most basic but that not withstanding, you can employ what works for you. In our case we want to use Express.

Webtask has a utility tool, webtask-tools, to help you bind your express app to a Webtask context. Therefore, rather than exporting a simple function, you can export an express app bound to Webtask using webtask-tools:

// ./index.js
var Express = require('express');
var Webtask = require('webtask-tools');
var bodyParser = require('body-parser')
var app = Express();

app.use(bodyParser.urlencoded({ extended: false }))
app.use(bodyParser.json())

// yet to be created
app.use(require('./middlewares/db').connectDisconnect);
require('./routes/stories')(app);

module.exports = Webtask.fromExpress(app);

If you are familiar with Express, you will see a lot of familiar codes. The big change is that rather than listening, we are exporting a function created from the Express app using webtask-tools.

The following lines are yet to be created but the handle database connection/disconnection and routes respectively:

...
app.use(require('./middlewares/db').connectDisconnect);
require('./routes/stories')(app);
...

Database, Connection, Schema and Model

We are trying as much as possible to be practical enough so you can have a 360 view of how useful Webtask can be. One of such practical situations is using an actual database rather than using a JSON temp file.

Mongolab Database

Mongolab by Object Labs is a good choice for cloud database because they eliminate the challenges of knowing how to setup a Cloud DB by just giving you a URL after choosing where you want your database to be hosted. To get started:

  • Quickly Sign up.
  • Create a database. A database URL will be supplied on the dashboard, copy and keep safe.
  • Create a user to authenticate the database. You can do this by clicking on the just created database, selecting the Users' tab and clicking Add database user.

Mongoose Schema

Mongoose is a library that makes interacting with Mongo DB easier. It provides a more friendly API for connecting, creating, updating and querying your database. To do this, it uses a schemas to map Mongo collections and their properties to JavaScript object.

// ./models/Story.js
const mongoose = require('mongoose');

module.exports = new mongoose.Schema({
    author: String,
    content: String,
    created_at: Date,
    id: mongoose.Schema.ObjectId
})

Connection and Model

Next is to connect to a database and disconnect at the beginning and end of each request. To set this up, we need to use a middleware which will execute before each of our Express routes:

// ./middlewares/db.js
var mongoose = require('mongoose');
// import Story schema
const StorySchema = require('../models/Story')

module.exports = {
    // Connect/Disconnect middleware
    connectDisconnect: (req, res, next) => {
        // Create connection using Mongo Lab URL
        // available in Webtask context
        const connection = mongoose.createConnection(req.webtaskContext.secrets.MONGO_URL);
        // Create a mongoose model using the Schema
        req.storyModel = connection.model('Story', StorySchema);
        req.on('end', () => {
            // Disconnect when request
            // processing is completed
            mongoose.connection.close();
        })
        // Call next to move to
        // the next Express middleware
        next()
    },
}

This middleware handles both connecting and disconnecting to a Mongolab database. The connection is achieved by passing a connection URL to the createConnection method. The URL is received via Webtask context; see next title.

When we establish a connection, we create a Mongoose model and attach the model to our request object.

We then attach an event listener to close the connection at the end of the request. next is called to pass down and continue with whatever middleware is next in the stack. This will most likely be an Express route.

Webtask Context

You can access contextual information via the Webtask context object. Such information can be used to store sensitive credentials like secrets, dynamic details like query strings, etc.

You can access the context via the function parameter:

module.exports = function(context, cb) {
    cb(null, { hello: context.data.name || 'Anonymous' });
}   

When using Express, you can access it from req.webtaskContext just like we saw in the database connection example. The MONGO_URL secret is passed in via the terminal while running the app:

wt create index --secret MONGO_URL=<MONGOLAB-CONNECTION-URL> --bundle

Express CRUD Routes

We have written all the helper codes our routes need to function. Tackling these routes is the next task:

// ./routes/stories.js
var mongoose = require('mongoose');

const Story = require('../models/Story');

module.exports = (app) => {
  app.get('/stories', (req, res) => {
      req.storyModel.find({}).sort({'created_at': -1}).exec((err, stories) => res.json(stories))
  });

  app.post('/stories', (req, res) => {
      const newStory = new req.storyModel(Object.assign({}, req.body, {created_at: Date.now()}));
      newStory.save((err, savedStory) => {
          res.json(savedStory)
      })
  })

  app.put('/stories', (req, res) => {
    const idParam = req.webtaskContext.query.id;
    req.storyModel.findOne({_id: idParam}, (err, storyToUpdate) => {
        const updatedStory = Object.assign(storyToUpdate, req.body);
        updatedStory.save((err, story) => res.json(story))
    })
  })

  app.delete('/stories', (req, res) => {
    const idParam = req.webtaskContext.query.id;
    req.storyModel.remove({_id: idParam}, (err, removedStory) => res.json(removedStory));
  })
}
  • GET: /stories route uses mongoose to fetch all the stories stored in the database and sort them by date created in descending order
  • POST: /stories is used to create a new story by storing the payload on req.body
  • PUT: /stories expects an id query string which it uses to find a story and updates the story based on the id
  • DELETE: /stories just like PUT, expects an id as well and removes an entry from the database collection based on the id

Final Words

You can hit the following URL using GET in Postman to see a response of an array if stories:


https://wt-nwambachristian-gmail_com-0.run.webtask.io/wt-mern-api/stories

Remember to run the app one more time if you have not been doing so to deploy your functions:

wt create index --secret MONGO_URL=<MONGOLAB-CONNECTION-URL> --bundle

The next part of this article will describe how we will consume these endpoints in a React app so as to have a full application. We will also deploy the React app so has to have everything running on a remote server.

Chris Nwamba

48 posts

JavaScript Preacher. Building the web with the JS community.