Serverless Tip #7 -- Create mini APIs with Cloud Functions and Express routing

Requirements:
  • an existing Google Cloud Platform account and project
  • Cloud Functions should be enabled for that project
Compared to the previous tip when using Exress' request path attribute, we can take advantage of Express routing.

So to support the following paths:

https://us-central1-myproject.cloudfunctions.net/api/customers
https://us-central1-myproject.cloudfunctions.net/api/customers/32
https://us-central1-myproject.cloudfunctions.net/api/customers/32/address

We can have our functions require Express by adding Express in package.json:

{
  "name": "mini-api-router",
  "version": "0.0.1",
  "dependencies": {
    "express": "^4.17.1"
  }
}

Then we can require that dependency in our new functions script:

// we use express explicitly
const express = require('express');
const app = express();
// some customer data retrieved from Firestore or elsewhere
const customers = {
  "32": { name: 'Alice', address: '21 Jump Street' },
  "33": { name: 'Bob', address: '1 Main Street' }
};
// this time we can define the path easily
app.get('/', (req, res) => 
        res.send('Hello World!'));
app.get('/customers', (req, res) => 
        res.status(200).json(customers).end());
// we can also specify path variables like :id 
// that we can retrieve via the request params object
app.get('/customers/:id', (req, res) => 
        res.status(200).json(customers[req.params.id]).end());
app.get('/customers/:id/address', (req, res) => 
        res.status(200).json({address: customers[req.params.id].address}).end());
// we need to export the app object for Cloud Functions to expose it
exports.api = app;

More information

Serverless Tip #6 -- Create a mini Web API with Cloud Functions

Requirements:
  • an existing Google Cloud Platform account and project
  • Cloud Functions should be enabled for that project
We often use individual HTTP Cloud Functions as a single endpoint, and we pass data to the functions with either query parameters, or via a POST body payload. Although it's a good practice to keep the scope of a function small, however, you can easily write mini Web APIs for a given function, with different paths for different needs, like with usual Web frameworks.

So instead of having just a single endpoint with:

https://us-central1-myproject.cloudfunctions.net/myfunction

You can have sub-paths below the name of your function:

https://us-central1-myproject.cloudfunctions.net/myapi/customers
https://us-central1-myproject.cloudfunctions.net/myapi/customers/32
https://us-central1-myproject.cloudfunctions.net/myapi/customers/32/address

Let's have a look at the Node functions runtime, and how you can implement this approach. The key trick here is to use the request path: req.path, which will give you the /customers/32 part of the fully qualified URL.

// some customer data retrieved from Firestore or elsewhere
const customers = {
  "32": { name: 'Alice', address: '21 Jump Street' },
  "33": { name: 'Bob', address: '1 Main Street' }
};
exports.myapi = (req, res) => {
  if (req.path.startsWith('/customers')) {
    const pathElements = req.path.split('/') // split along the slashes
      .filter(e => e)                        // remove the empty strings in the array
      .splice(1);                            // remove the first "customers" element
    // path: /customers
    if (pathElements.length == 0) { 
      // return all customers
      res.status(200).json(customers).end();
    }
    // path: /customers/32
    else if (pathElements.length == 1) {
      res.status(200).json(customers[pathElements[0]]).end();
    }
    // path: /customers/33/address
    else if (pathElements.length == 2 && pathElements[1] == "address") {
      res.status(200).json({address: customers[pathElements[0]].address}).end();
    }
  }
  res.status(404).send('Unknown path').end();
};

In the Node.JS runtime, Cloud Functions uses the Express framework under the hood. We have access to the request object, which has lots of useful attributes, including the path.

In this simplistic example, we are using this path attribute directly, but it's also possible to use more advanced routing capabilities, as we shall see in a forthcoming tip.

More information

Serverless Tip #5 -- How to invoke a secured Cloud Run service locally

Requirements:
  • an existing Google Cloud Platform account with a project
  • you have enabled the Cloud Run service and already deployed a container image
  • your local environment's gcloud is already configured to point at your GCP project
By default, when you deploy a Cloud Run service, it is secured by default, unless you use the --allow-unauthenticated flag when using the gcloud command-line (or the appropriate checkbox on the Google Cloud Console).

But once deployed, if you want to call it locally from your development machine, for testing purpose, you'll have to be authenticated.

If you look at the Cloud Console, alongside the URL of the service, you can hover the little icon next to the URL, and you'll see the a pop-up showing how you can invoke that service with a curl command:

curl -H \
    "Authorization: Bearer $(gcloud auth print-identity-token)" \
    https://authenticated-x2rq3lgmra-uc.a.run.app
Note how a bearer token generated by the gcloud command is passed as header to the curl request.

More information

Serverless Tip #4 -- Discover the full URL of your deployed Cloud Run services with gcloud's format flag

Requirements:
  • an existing Google Cloud Platform account
  • you have enabled the Cloud Run service and deployed already a container image
One of the nice things with Cloud Run is that when you deploy your services, you get a URL like https://myservice-8oafjf26aq-ew.a.run.app/, with a certificate, on the run.app domain name, etc.

You see the name of the service: myservice, the region shortcut where it was deployed: ew (Europe West), and then .a.run.app. However, you can't guess ahead of time what the final URL will be, as there is a randomly generated part in the URL (here: 8oafjf26aq). Let's see how we can discover this whole URL.

From my terminal, I can request the list of deployed services (here, on the fully managed Cloud Run):

gcloud run services list --platform managed

It's going to show me something like the following output:

   SERVICE      REGION        URL                                        LAST DEPLOYED BY     LAST DEPLOYED AT
✔  myservice    europe-west1  https://myservice-8oafjf26aq-ew.a.run.app  myself@foobar.com    2019-11-20T15:26:39.442Z

When describing the specific service (I had to specify the region as well, but you can set it by default if needed to avoid repeating yourself):

gcloud run services describe myservice --platform managed --region europe-west1

You'll see:

✔ Service hello in region europe-west1
https://myservice-8oafjf26aq-ew.a.run.app
Traffic:
  100%               LATEST (currently myservice-00002-dox)
Last updated on 2019-11-20T15:26:39.442Z by myself@foobar.com:
  Revision myservice-00002-dox
  Image:             gcr.io/my-sample-project/my-container-image:latest

So instead of parsing that ourselves somehow, there's a built-in way to get just the info we want, with the useful --format flag:

gcloud run services describe myservice --format='value(status.url)' --platform managed --region europe-west1 

This time, in output, you'll get just the URL, which you can then export or reuse with other commands.

https://myservice-8oafjf26aq-ew.a.run.app

The glcoud command provides three useful mechanisms to filter, format, or project the output and values returned. Here, we took advantage of format.

More information:

Serverless Tip #3 -- Use the Cloud Run button on your Git repository to deploy your project in a click

Requirements:
  • an existing Google Cloud Platform account
  • a Git or Github repository containing your project
  • your project can have a Dockerfile (but not mandatory)
With Cloud Run, you can easily deploy a container image and let it scale up and down as needed, in a serverless fashion:
  • No need to focus on infrastructure (provisioning servers, clusters, upgrading OS, etc.)
  • Your application can scale transparently from 0 to 1, and from 1 to n (no need for a pager when your app is featured on Hackernews)
  • You pay as you go, proportionally to the usage
If your project is hosted on Github, for example, how can you help users get started with your project? You usually explain all the steps needed to build a container image, or where to fetch a pre-made image from a hub, and then steps to actually deploy that image on the platform. But thanks to the Cloud Run button, you can add a button image on your README.md page for instance, and then users can click on it and get started with building and deploying to a GCP project automagically.


If the Git repository contains a Dockerfile, it will be built using the docker build command. Otherwise, the CNCF Buildpacks (with the pack build command) will be used to build the repository.

The Cloud Run button github project gives extra information on the parameterization of the deploy URL of the button, for example if you want to specify a particular branch or directory.

More information
 
© 2012 Guillaume Laforge | The views and opinions expressed here are mine and don't reflect the ones from my employer.