Creating a MongoDB CRUD backend on Google Cloud Functions.

In the last 2 weeks I have been tinkering with the Cloud Functions from Google. After seeing a great introductory talk and demos on the SF Serverless Meetup by Bret McGowen from Google (https://twitter.com/bretmcg) I decided give it a shoot.

So what are Google Cloud Functions anyway?

Google Cloud Functions is a Functions as a Service (FaaS) offering by Google, billing to the nearest 100 millisecond, running Javascript.

First Google Cloud Functions, Hello World?

This is, of course, a dummy example, but it works!

This is a basic JavaScript module. Exporting a function named handler. This function is very similar to an Expressjs (https://expressjs.com/) handler function. So, anything that works on Express, works here too. (GCF is actually using Expressjs to power their HTTP functions)

GCF has a minimum set of requirements. Like:

  • package.json file to manage dependencies, which will be installed at deploy time.
  • Node 6.9.1 (its a bummer we can’t yet use later versions directly, but Babel can help us here)
  • GCF SDK installed and initialized (https://cloud.google.com/sdk/downloads#interactive)
  • A Google Cloud Storage Bucket to upload or code (Uploading functions from Git is also possible)

The deploy script gets the function from the “main” prop on the package.json file.

{
"name": "gcf-hello",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC"
}

Once we have all the bases covered, we need to create a deploy bucket. A little command will help us there:

$ gsutil mb gs://medium-post-functions

And then, just deploy the function with a “simple” command like:

$ gcloud beta functions deploy hello-world --entry-point handler --trigger-http --stage-bucket medium-post-functions --memory=256MB
Copying file:///var/folders/f9/_xttz7rs7t10grd6_q_8s1dr0000gn/T/tmpxV6pSu/fun.zip [Content-Type=application/zip]...
- [1 files][ 438.0 B/ 438.0 B]
Operation completed over 1 objects/438.0 B.
Deploying function (may take a while - up to 2 minutes)...done.
availableMemoryMb: 256
entryPoint: handler
httpsTrigger:
url: https://us-central1-revelatio-165320.cloudfunctions.net/hello-world
latestOperation: operations/cmV2ZWxhdGlvLTE2NTMyMC91cy1jZW50cmFsMS9oZWxsby13b3JsZC9nb0pHUXV2TXFTbw
name: projects/revelatio-165320/locations/us-central1/functions/hello-world
serviceAccount: revelatio-165320@appspot.gserviceaccount.com
sourceArchiveUrl: gs://medium-post-functions/us-central1-hello-world-bsesoemdvxwb.zip
status: READY
timeout: 60s
updateTime: '2017-05-15T16:20:11Z'
$

We will get a URL to call our function via HTTP.

$ curl https://us-central1-revelatio-165320.cloudfunctions.net/hello-world
Hello World
$

Adding some dependencies to make it useful

First, I would love to write this functions on a more up to date ES2015 JavaScript. Lets add Babel (https://babeljs.io/) to the mix.

$ yarn add --dev babel-cli babel-core babel-plugin-transform-runtime babel-preset-es2015 babel-preset-stage-1
$ yarn add babel-runtime

And a .babelrc file:

{
"presets": ["es2015", "stage-1"],
"plugins": ["transform-runtime"]
}

After this we can also add a script section on our package.json file to make a build of our code (and another to build and deploy)

{
"name": "gcf-hello",
"version": "1.0.0",
"description": "",
"main": "lib/index.js",
"scripts": {
"build": "rm -rf lib/ && `yarn bin`/babel index.js --out-dir ./lib",
"deploy": "yarn build && gcloud beta functions deploy hello-world --entry-point handler --trigger-http --stage-bucket medium-post-functions --memory=256MB"
},

"author": "",
"license": "ISC",
"dependencies": {
"babel-runtime": "^6.23.0"
},
"devDependencies": {
"babel-cli": "^6.24.1",
"babel-core": "^6.24.1",
"babel-plugin-transform-runtime": "^6.23.0",
"babel-preset-es2015": "^6.24.1",
"babel-preset-stage-1": "^6.24.1"
}

}

The text marked bold represents the changes to our package.json file.

So now we can write our function with all the nicer ES2015

export const handler = (req, res) => res.send('Hello World')

And now, just:

$ yarn deploy

You can redeploy as many times as you want. The same URL always comes back to use our HTTP function.

Lets make a MongoDB CRUD API backend

First we need to add the mongodb (https://www.npmjs.com/package/mongodb) driver module.

$ yarn add mongodb dotenv

I also added one more dependency here.

I personally prefer to use mLab.com for a hosted mongodb service with a free tier (no need for more that 500Mb), you can have your mongodb DB anywhere, accesible from Internet of course. MongoDB Atlas is another service you could use.

.env file will help us configure dynamic properties, including the MONGODB connection string. This file should be keep out of versioning control. (Ex. .gitignore)

MONGODB=mongodb://gcf:KjyCaENsTT6q8PVE@ds143231.mlab.com:43231/gcf-hello

Deploy again and test it from the console

$ curl https://us-central1-revelatio-165320.cloudfunctions.net/hello-world
[{"_id":"591a25ea734d1d1cd0becda2","name":"Ernesto F.","email":"ernestofreyreg@gmail.com"}]

Now we have the R part from CRUD. Lets try to add the C part (create)

$ yarn deploy

and…

$ curl -d '{"name":"Lucian Freyre", "email":"lucian@codexsw.com"}' -H "Content-Type: application/json" -X POST https://us-central1-revelatio-165320.cloudfunctions.net/hello-world
{"result": "ok"}
$ curl https://us-central1-revelatio-165320.cloudfunctions.net/hello-world
[{"_id":"591a25ea734d1d1cd0becda2","name":"Ernesto F.","email":"ernestofreyreg@gmail.com"},{"_id":"591a2e757b0c5400028ad0c3","name":"Lucian Freyre","email":"lucian@codexsw.com"}]

So far so good, now we have our endpoint working, 2 basic operations (Create and Read). I wont add more ops here, you probably already figured how to add the rest.

Final thoughts

  • FaaS are here to stay.
  • Google Cloud Functions could improve a lot, especially in tooling, documentation and performance.

Next steps

  • GraphQL (http://graphql.org/) of course :) considering we are actually using Expressjs as backend the rest should be fairly easy using the right graphql modules.
  • And server side rendered React.