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=256MBCopying 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.
- dotenv (https://www.npmjs.com/package/dotenv), allow us to load configuration variables into process.env
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.
A .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.