Cloud Functions is Google's entry into the serverless computing craze. The promise of the serverless architecture is to dramatically reduce the cost and complexity of developing, scaling, and operating a range of applications. While there are still a host of issues to resolve, the potential benefits are enormous, especially for microservices, IoT, and mobile.
This article and companion github repo show how to use Cloud Functions as the backend for a React app that can be deployed as a static website. While frameworks like Serverless provide more features, such as built-in deployment and a variety of useful plugins, the approach presented here is a simple and quick way to get a basic frontend and backend up and running quickly.
The following sections will walk you through the set-up of a simple example that uses cloud functions to query an API (the list of articles on oreilly.com), and an accompanying React frontend that will let you page through the data.
The frontend will also show how to:
- Set up redux, a library to manage your JavaScripts application state in a central datastore
- Set up middleware to load data from our backend; it also shows how to display a spinner while the data is being fetched
- Use the material-ui framework to create a super simple UI
- Dispatch events to a web page through the API data
Before you start, you'll need to:
- Create a google cloud account
- Install the Google cloud SDK so that you can manage your cloud from the command line.
- Install (if you don't already have) Node version 6 or higher.
- Install (if you don't already have) create-react-app, Facebook's awesome utility for bootstrapping, developing, and building React projects.
- Clone odewahn/cloud-functions-with-react from GitHub,
cd
into it, and runnpm install
.
Once you've got these pieces in place, move on to the next section.
Google Cloud organizes projects into, well, projects. So the first step is to create a new one. Since this is intended as a basic template to get started, I'll calling it react-gcloud-template
:
gcloud projects create react-gcloud-template
This takes about 30-45 seconds. Then you can set it to be the default project for all subsequent gcloud
commands:
$ gcloud config set core/project react-gcloud-template
The cloud functions emulator allows you to develop and test functions on your local computer, which vastly accelerates development speed.
npm install -g @google-cloud/functions-emulator
Once it's installed, set your new project:
functions config set projectId react-gcloud-template
Next, start the emulator:
functions start
Finally, deploy the posts
function to the emulator, like this:
functions deploy posts --trigger-http
The posts
function, which is the user's main entry-point for the example, is defined in the root level file index.js
of the project directory, and uses the node-fetch module to grab a page-by-page list of articles from oreilly.com:
var fetch = require("node-fetch");
const cors = require("cors")();
//===========================================================================
// Get the topic types from ORM site
//===========================================================================
exports.posts = (req, res) => {
// Parse the body to get the passed values
var params;
try {
// try to parse the body for passed parameters
params = JSON.parse(req.body);
} catch (e) {
// provide default values if there is an error
params = { page: 1 };
}
// Wrap function in a CORS header so is can be called in the browser
cors(req, res, () => {
// Lookup the account type based on the email.
fetch(
"https://gateway.oreilly.com/clients/website/feed/all/page/" +
params["page"],
{}
)
.then(res => {
return res.json();
})
.then(data => {
res.send(data["posts"]);
});
});
};
Two small but important things to observe. First, note how the return function is wrapped in a cors
function call; this adds the headers required to get around the browser's cross-origin security restrictions. Without these headers, you'll get this error on any AJAX/fetch call from the browser:
Fetch API cannot load http://localhost:8010/react-gcloud-template/us-central1/posts. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:3000' is therefore not allowed access. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
Second, notice how we're only returning a part of the data we get back (just the values in data["posts"]
). This shows how we can use the Cloud Function to transform a raw API call, which can be valuable when you're developing microservices against a larger API infrastructure.
Here's an example of how to use httpie to call the function from the command line. The value returned is a JSON-formatted array of hashes; each hash contains the data for a post:
http -b http://localhost:8010/react-gcloud-template/us-central1/posts page=1
[
{
"byline": {
"authors": [
{
"first-name": "Hadi",
"last-name": "Hariri",
"url": "/people/7fd97-hadi-hariri"
}
]
},
"dek": "Learn how Kotlin functions easily accept default and multiple parameters.",
"permalink": "https://www.oreilly.com/learning/simple-functions-in-kotlin",
"title": "Simple functions in Kotlin"
},
...
]
You can also add new functions to index.js
, using posts
as a template. Just be sure that you deploy each one individually (i.e., functions deploy yourNewFunction --trigger-http
). Once deployed, all your changes will be "live" in the emulator each time you update and save the file, so that you'll be executing the new code once you call the function again. This is a huge time savings compared to having to deploy the function directly to Google Cloud, and one of the key strengths of using the functions
emulator.
Once your function is works, you can deploy it to Cloud Functions (versus just the emulator). To do this, you'll need to enable billing and Cloud Functions in the cloud console.
After you've enabled everything, create a cloud storage bucket where your code will be uploaded. (This bucket will also have the node_modules
folder for all your dependencies.) Go into the console and create a bucket where your code where your code can be uploaded to gcloud. Alternatively, you can also use gsutil tool, a command line utility for working with gCloud:
gsutil mb -p react-gcloud-template gs://react-gcloud-template-deploy
In either case, you must use a globally unique bucket name. I called mine ano-auth-test-bucket
.
(If you get an error like AccessDeniedException: 403 The account for bucket "react-gcloud-template-deploy" has been disabled
you need to enable billing on the project in the console.)
Remember to run npm install
to download any dependencies. Then you’re ready to deploy your application in the cloud):
gcloud beta functions deploy posts --stage-bucket react-gcloud-template-deploy --trigger-http
That will produce a log like this:
Copying file:///var/folders/hj/x05v_3s544n68fqr31pxsjwr0000gn/T/tmpFaEd0v/fun.zip [Content-Type=application/zip]...
/ [1 files][ 3.9 MiB/ 3.9 MiB]
Operation completed over 1 objects/3.9 MiB.
Deploying function (may take a while - up to 2 minutes)...done.
availableMemoryMb: 256
entryPoint: posts
httpsTrigger:
url: https://us-central1-react-gcloud-template.cloudfunctions.net/posts
latestOperation: operations/cmVhY3QtZ2Nsb3VkLXRlbXBsYXRlL3VzLWNlbnRyYWwxL3Bvc3RzLzNfQXlBNGR2LUhN
name: projects/react-gcloud-template/locations/us-central1/functions/posts
serviceAccount: react-gcloud-template@appspot.gserviceaccount.com
sourceArchiveUrl: gs://react-gcloud-template-deploy/us-central1-posts-unmlnlxxrudy.zip
status: READY
timeout: 60s
updateTime: '2017-06-28T14:18:12Z'
The httpsTrigger
section of the log has the URL for the deployed function:
http https://us-central1-react-gcloud-template.cloudfunctions.net/posts
If you forget this link, you can always recover it like this:
$ gcloud beta functions list
NAME STATUS TRIGGER
posts READY HTTP Trigger
And then describe
it, like so:
$ gcloud beta functions describe posts
availableMemoryMb: 256
entryPoint: posts
httpsTrigger:
url: https://us-central1-react-gcloud-template.cloudfunctions.net/posts
latestOperation: operations/cmVhY3QtZ2Nsb3VkLXRlbXBsYXRlL3VzLWNlbnRyYWwxL3Bvc3RzLzNfQXlBNGR2LUhN
name: projects/react-gcloud-template/locations/us-central1/functions/posts
serviceAccount: react-gcloud-template@appspot.gserviceaccount.com
sourceArchiveUrl: gs://react-gcloud-template-deploy/us-central1-posts-unmlnlxxrudy.zip
status: READY
timeout: 60s
updateTime: '2017-06-28T14:18:12Z'
Once you've set up the backend functions, you're ready to tackle the frontend. If you're starting completely from scratch (as opposed to cloning from an existing project), you'll need create-react-app, a tool from Facebook that claims to let you "create React apps with no build configuration." Basically, it's an opinionated way to structure your projects and tools.
npm install -g create-react-app
If you haven't already, run npm install
to download all your dependencies into the node_modules
directory.
Once npm install
completes, run npm start
to fire up the app. Note that you may need to modify the URL in src/state/posts.js
to match your functions emulator:
return fetch(
"http://localhost:8010/react-gcloud-template/us-central1/posts",
{
method: "POST",
body: JSON.stringify({
page: getState().Posts.get("page")
})
}
)
From then on, you can make changes in the React app and thanks to the magic of create-react-app
, you'll get immediate hot reloading for your project.
A quick description follows of how the project is organized:
- All the frontend code is in the
src
directory, which is the default fromcreate-react-app
- The main entry point,
src/App.js
, sets up theredux
store and the redux-thunk middleware. Redux, a library by Dan Abramox, allows you to centralize your application state and all logic that alters that state into one central store, leading to more maintainable code. - The
src/state
directory has all the redux stores. I like to usecombine-reducers
so that I can break out the state tree into logical units. With that structure, you can easily add a new kind of state objects (for example, acontent
store with the contents of a post) by putting it in a new file (you can usesrc/state/posts.js
as a template) and adding it into the combined reducers insrs/state/index.js
. - All the state logic for this sample is in
src/state/posts.js
. In addition to the ability to dispatch a thunk that retrieves data from the cloud function, I've also set up logic to control a spinner/progress meter. In production code, you'd want to also do things like handling errors, but that's beyond the scope of this article. - The main UI component is in
src/components/posts.js
. This shows how to use the material-ui toolkit to make a simple UI that consists of a list based on the API results, along with a couple of nav buttons to page though the results.
Once you're ready to deploy the React app, you can run npm run build
, unpo which react-create-app
will create a compiled, minified app in a build
directory that is ready to publish as a static site. For example, you could commit the files in build
to the top level of a gh-pages
branch of a new project and publish your app with GitHub pages.
The combination of the serverless architecture with a React frontend published as a static site is a great way to quickly spin up new applications with minimal administrative overhead. However, serverless really shines for quickly deploying microservices and IoT or mobile backends. The superior economics of cloud functions over traditional hosting present many exciting opportunities, so watch this space carefully.