If youâre someone whoâs worked with serverless, you would know itâs addictive. The simplicity of pushing your code and having it work without the hassle of provisioning servers and the ability to scale automatically is something you miss when switching to a traditional REST API setup.
While AWS Lambda popularised the serverless concept and framework, I find Google Cloud Functions much better in terms of the entire development loop and ease of deployments, rollouts and updates (Minus the cold start, which AWS Lambda is significantly better at).
Letâs say you are someone at an early-stage startup and decide to create a backend API with Express, you host it on an EC2 instance and lifeâs good until 2 months later when your EC2 instance hits its memory and storage limits (Storage runs out on an EC2 instance for some reason) and itâs no longer able to serve requests for your app thatâs booming. Now you could set up auto-scaling but thatâs too much of a hassle and too confusing (AWS after all), hard to get right, time-consuming and budget-draining for a small team of engineers.
But you couldnât just wake up one day and decide âLet me move my Express app to a serverless platform, that way no hassles of managing memory and trafficâ.
There are ways to dockerize your application and serve it via a platform like Cloud Run, AWS EKS or ECS that handle traffic surges for you automatically. But even then, too complex and hard to get right. In those times, one wishes for a way to deploy their existing backend app to Lambdas (Or cloud functions if youâre me).
Well now you can, in this post, Iâll show you how you can deploy your existing Express app straight to Google Cloud Functions and reap the benefits of Serverless without a single structural change.
Let me make something clear, this is a solution that would probably suit a single dev or a small team very well. But with a larger dev team with more complex use cases, you might want to assess whether a one-size-fits-all cloud function would work for you. No two needs are the same in tech and we have complex-yet-robust architectures of backend microservices for a reason.
For those that have come from Firebase land, it might be a little surprising to see another Cloud Functions offering from Google themselves. Well, the simplest answer is Firebase Cloud Functions acts as an opinionated wrapper around your code and takes care of deployments automatically for you without any added configuration, you still run your code as a Google Cloud Function at the end of the day, just with a neater and âtaken-care-of-for-youâ interface provided by Firebase.
Firebase Cloud Functions only support JavaScript/TypeScript with Express whereas Google Cloud Functions support a lot more (For instance itâs a good solution for fans of Python with Flask, or even those that build APIs in Java).
A benefit you get with this is that traditional Firebase Cloud Functions act as a framework with guidelines around how to structure your backend into micro-services that get deployed independently, with Google Cloud Functions you have the flexibility to do whatever you want, deploy whatever you want.
stateful
like files or in-memory stores. Cloud Functions execute independently, which means they more often than not will not share memory space with each other in most cases (There are exceptions sometimes where a cloud function is pooled in the same memory space as its parallelly executing function instances), same goes with storage, Cloud Functions have a File System but it is ephemeral and goes away as soon as your Cloud Function terminates.Enable the Cloud Functions API (It should enable the Cloud build APIs to build your code on the server).
build
script in your package.json file.
"scripts": {
+ "build": "tsc",
...
}
main
field in your package.json file and point it to the final JS file youâll execute your express from.
"name": "my-express-api"
+ "main": "./build/index.js" // Will be the final index file for your app
app.listen
function call in your express file or hide it behind a local-dev check with something like proces.env.NODE_ENV
. With Cloud Functions, the app listens automatically for you on a pre-defined port, you donât have to add it yourself and doing so will actually cause your function to crash once deployed....
...
+ export { app };
Install the gcloud
command line utility that makes it easy for you to work with Google Cloud APIs and login to your account using gcloud init
.
gcloud functions deploy <name-of-your-function>
--timeout=100
--trigger-http
--entry-point app # Should be the name of your exported variable
--region=asia-south1 # You can check the list of regions supported by GCP
--allow-unauthenticated
--runtime=nodejs18
âWell, what happens to my environment variables?â you might ask.
Itâs actually quite simple, to the deployment command, you can add the following:
--set-env-vars VAR_NAME=<var_value>,VAR_NAME2=<var_value>...
You can even specify your .env file for the function:
--env-vars-file .env
OR Thereâs another elegant way but that requires some manual work:
After this is done and the function is re-deployed, your process.env.VAR_NAME
expressions will start working. Locally there shouldnât be any changes required.
Now that weâve nailed down how to write and deploy a Cloud Function manually, we can now do the same thing via our CI/CD pipelines.
While the specific deployment code would differ for your CI/CD provider, hereâs an outline of how I do it in a GitHub action.
First, whichever CI/CD provider you choose, you will need a way for it to communicate with Google Cloud and for Google Cloud to verify that the actions are indeed happening on your behalf, you can do so by using a service account generated for your account and using it to initialize the gcloud
command line on GitHub actions with it.
Generate the Service Account from your Google Cloud Console using the steps above and add it to your GitHub repositoryâs secrets.
name: Deploy API to Google Cloud Function
on:
- workflow_dispatch # Manual deployment
jobs:
deploy:
name: Deploy API to Google Cloud Function
runs-on: ubuntu-latest
env:
GCLOUD_PROJECT_ID: $
GOOGLE_CREDENTIALS: $
steps:
- name: "Set up Cloud SDK"
uses: "google-github-actions/setup-gcloud@v1"
with:
version: ">= 363.0.0"
- id: "auth"
name: "Authenticate to Google Cloud"
uses: "google-github-actions/auth@v1"
with:
credentials_json: "$"
- name: Run Deployment Script
run: |
gcloud config set project $GCLOUD_PROJECT_ID
gcloud functions deploy <functionName> --timeout=100 --trigger-http --entry-point <name of app export, ex: app> --region=asia-south1 --allow-unauthenticated --runtime=nodejs18
With this deployment script, you should be set to deploy your backend to Cloud Functions using a single click!