Developing a Firebase Function Pt 1 - Initialize and Deploy

Recently, I've been dabbling in Google Firebase due to its minimal overhead in getting started and relatively simple deployment pipeline. In particular, its support for NodeJS code and HTTPS makes it an ideal candidate for folks who are new to API development and looking for a low-cost option. In this post, I wish to demonstrate from start to finish how to start a NodeJS API, be able to test it, and deploy it to production.

Installation and Initialization

To get started, check to ensure you have the Firebase CLI installed on your computer. To install the Firebase CLI, please follow these instructions. Once you are done, ensure you have installed Firebase version 8.6.0

$ firebase --version 
8.6.0

If you do not have version 8.6.0 installed, you can install this version globally using the following command with NPM:

$ npm install -g [email protected]
$ npm install -g [email protected]

NOTE: If you receive an EACCESS: permission denied error running the npm install commands, use sudo npm install... instead.

Now, head over to Firebase and sign in. If you've never used Firebase before, all you need is to log into a Google account.

Using the Firebase CLI, log into your Firebase account using a command line application.

$ firebase login

? Allow Firebase to collect CLI usage and error reporting information (Y/n): N

This will open a page in your browser to login to Firebase and grant permissions for the CLI to access your account. Once you've signed in, the CLI will have the credentials stored and you're ready to initialize your project.

Create a new folder and visit this folder using your command line app. Then, use the script below to start the initialization:

$ firebase init

Once this command is entered, it will prompt you for which kind of project you want to create. For this tutorial, we will only be making a Functions project. Using the arrow keys, go down to Functions, hit space, and then hit enter. Next, you will be asked if you want to create a new project or use an existing project. Choose 'Create a new project', supply a unique project name (the initialization will fail if the name is already taken), and then choose a name to call your project (if left blank, it will default to the project name).

Then, You will then be asked if you want to use JavaScript or TypeScript. We will use JavaScript for this project. Then, when asked if you want to use ESLint to catch probable bugs or enforce style, choose No. When asked if you want to install dependencies with npm, choose Yes.

$ firebase init

You're about to initialize a Firebase project in this directory:

  /Users/matt/Documents/test

? Which Firebase CLI features do you want to set up for this folder? Press Space to select features, then Enter to confirm your choices. Functions
: Configure and deploy Cloud Functions

=== Project Setup

First, let's associate this project directory with a Firebase project.
You can create multiple project aliases by running firebase use --add, 
but for now we'll just set up a default project.

? Please select an option: Create a new project
i  If you want to create a project in a Google Cloud organization or folder, please use "firebase projects:create" instead, and return to this command when you've created the project.
? Please specify a unique project id (warning: cannot be modified afterwa
rd) [6-30 characters]:
 irbytestproject
? What would you like to call your project? (defaults to your project ID)

βœ” Creating Google Cloud Platform project
βœ” Adding Firebase resources to Google Cloud Platform project

πŸŽ‰πŸŽ‰πŸŽ‰ Your Firebase project is ready! πŸŽ‰πŸŽ‰πŸŽ‰

Project information:
   - Project ID: irbytestproject
   - Project Name: irbytestproject

Firebase console is available at
https://console.firebase.google.com/project/irbytestproject/overview
i  Using project irbytestproject (irbytestproject)

=== Functions Setup

A functions directory will be created in your project with a Node.js
package pre-configured. Functions can be deployed with firebase deploy.

? What language would you like to use to write Cloud Functions? JavaScript
? Do you want to use ESLint to catch probable bugs and enforce style? No
βœ”  Wrote functions/package.json
βœ”  Wrote functions/index.js
βœ”  Wrote functions/.gitignore
? Do you want to install dependencies with npm now? Yes

added 255 packages, and audited 256 packages in 5s

i  Writing configuration info to firebase.json...
i  Writing project information to .firebaserc...
i  Writing gitignore file to .gitignore...

Inside of your project folder, you will now see a folder called functions. If you check the package.json file within this folder, you should see something very similar to this:

{
  "name": "functions",
  "description": "Cloud Functions for Firebase",
  "scripts": {
    "serve": "firebase emulators:start --only functions",
    "shell": "firebase functions:shell",
    "start": "npm run shell",
    "deploy": "firebase deploy --only functions",
    "logs": "firebase functions:log"
  },
  "engines": {
    "node": "10"
  },
  "dependencies": {
    "firebase-admin": "^8.10.0",
    "firebase-functions": "^3.6.1"
  },
  "devDependencies": {
    "firebase-functions-test": "^0.2.0"
  },
  "private": true
}

There are a couple of items to note. First, you will see commands such as firebase emulators:start and firebase deploy. Respectively, these commands will allow you to run your functions locally and deploy the functions. The --only functions flag at the end of these commands specifies that you only want the functions folder to be deployed to Firebase.

Running your API locally

Let's run our API and see it do some work. Run the following command to start your emulator:

$ firebase emulators:start

i  emulators: Starting emulators: functions
⚠  functions: The following emulators are not running, calls to these services from the Functions emulator will affect production: firestore, database, hosting, pubsub
⚠  Your requested "node" version "10" doesn't match your global version "12"
i  ui: Emulator UI logging to ui-debug.log
i  functions: Watching "/Users/matt/Documents/test/functions" for Cloud Functions...

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ βœ”  All emulators ready! View status and logs at http://localhost:4000 β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Emulator  β”‚ Host:Port      β”‚ View in Emulator UI             β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Functions β”‚ localhost:5001 β”‚ http://localhost:4000/functions β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
  Other reserved ports: 4400, 4500

Now, depending on how your emulator is configured, you may see a different value in your Host:Port box. In my instance, I have localhost:5001 set up as my function's host and port. When I visit http://localhost:5001 in my browser, however, I get the following message:

Cannot GET /

What this tells me is that my API is active, but the endpoint I'm trying to reach isn't available for GET. Let's look back at the project and see why this is the case.

Within the functions folder, in addition to the package.json file there's another file called index.js. The index.js file serves as the entry point of our API. When I open the index.js file, I see the following:

const functions = require('firebase-functions');

// // Create and Deploy Your First Cloud Functions
// // https://firebase.google.com/docs/functions/write-firebase-functions
//
// exports.helloWorld = functions.https.onRequest((request, response) => {
//   functions.logger.info("Hello logs!", {structuredData: true});
//   response.send("Hello from Firebase!");
// });

There are two things I notice. The first is, at the top, there's an import a firebase-functions package. The second thing I notice is that the code below it is commented out.

The firebase-functions package is an SDK that provides you with access to Cloud Functions for Firebase. It allows you to access things such as environment variables (which we will look further into in the next post) and defining HTTP routes.

In the commented out code, there's an command to export an endpoint called helloWorld, and it's pointing to a functions command to route an HTTP request to a function which will log Hello logs! and then send a response back saying Hello from Firebase!.

Let's un-comment this code and see what happens.

const functions = require('firebase-functions');

// Create and Deploy Your First Cloud Functions
// https://firebase.google.com/docs/functions/write-firebase-functions

exports.helloWorld = functions.https.onRequest((request, response) => {
  functions.logger.info("Hello logs!", {structuredData: true});
  response.send("Hello from Firebase!");
});

While the emulator is running, it will detect any saved changes made and will automatically reload the project. Checking my command line, I see the following message appear

functions[helloWorld]: http function initialized (http://localhost:5001/irbytestproject/us-central1/helloWorld).

When I visit the URL in my browser, I get a plain HTML page that says 'Hello from Firebase!'. Neat! My API is working!

Now let's check the Terminal to see if the log command is captured:

i  functions: Beginning execution of "helloWorld"
>  {"structuredData":true,"severity":"INFO","message":"Hello logs!"}
i  functions: Finished "helloWorld" in ~1s

Now that we can see our API is working and logs are being captured, let's move onto deploying our API.

Deploying the API

While it's not much (for now), we should be proud of the progress we've made. I think the world is ready to see our creation.

Let's stop our emulator (either by using Ctrl+C or Cmd+C) and use the firebase deploy command. Since we only have functions in our project, we don't need to worry about specifying the --only functions flag.

$ firebase deploy


=== Deploying to 'irbytestproject'...

i  deploying functions
i  functions: ensuring required API cloudfunctions.googleapis.com is enabled...
i  functions: ensuring required API cloudbuild.googleapis.com is enabled...
βœ”  functions: required API cloudfunctions.googleapis.com is enabled
⚠  functions: missing required API cloudbuild.googleapis.com. Enabling now...

Error: HTTP Error: 400, Billing account for project [removed] is not found. Billing must be enabled for activation of service(s) 'cloudbuild.googleapis.com,containerregistry.googleapis.com' to proceed.
Help Token: ...

Looks like we've run into an issue due to our project not being setup for billing yet. Not a problem. To fix this, let's go to our console by visiting https://console.firebase.google.com/, go into your project and click on Functions, and click on Upgrade project.

Select the Blaze - Pay as You Go option, enter in your billing information, and purchase.

Once this is done, we are ready to run our deploy code again. Note that it may take a couple of minutes for your project upgrade to take effect, and then deploying may take several minutes especially if it's the first time deploying.

$ firebase deploy

=== Deploying to 'irbytestproject'...

i  deploying functions
i  functions: ensuring required API cloudfunctions.googleapis.com is enabled...
i  functions: ensuring required API cloudbuild.googleapis.com is enabled...
⚠  functions: missing required API cloudbuild.googleapis.com. Enabling now...
βœ”  functions: required API cloudfunctions.googleapis.com is enabled
βœ”  functions: required API cloudbuild.googleapis.com is enabled
i  functions: preparing functions directory for uploading...
i  functions: packaged functions (54.5 KB) for uploading
βœ”  functions: functions folder uploaded successfully
i  functions: creating Node.js 10 function helloWorld(us-central1)...
βœ”  functions[helloWorld(us-central1)]: Successful create operation. 
Function URL (helloWorld): https://us-central1-irbytestproject.cloudfunctions.net/helloWorld

βœ”  Deploy complete!

Now, if we check out our Firebase Functions page again, we can now see a Functions entry for helloWorld:

Let's go test out our API! Copy the URL for your function and put it into your browser. You should see a plain HTML page that says Hello from Firebase!

Conclusion

We have walked through initializing our Firebase Functions project, running it locally, and deploying it to Firebase. In my next article, I will walk through some more concepts such as creating environment variables and handling different types of HTTP methods such as POST, PUT, etc.

19