Getting started with Firebase Cloud Functions

Firebase has evolved considerably since its early days, and Cloud Functions are now a core part of the platform. Before diving into more complex configurations, it helps to strip things down to the absolute minimum required to get a function deployed and responding to HTTP requests. This guide walks through that process step by step, from project creation to automated deployment with GitHub Actions.

Prerequisites

You'll need a GitHub account, a Firebase account (your Google account works), and a fresh repository cloned locally.

Setting up the Firebase project

Start by creating a new project in the Firebase console. The options you select during setup don't matter for this exercise. After the project exists, note its ID—it appears in the console URL and can be modified during project creation. You'll reference this ID in configuration files later.

To mark your local repository as a Firebase project, create a firebase.json file at the root with an empty object:

{}

The association between your repository and the Firebase project lives in a .firebaserc file. This is what tells firebase deploy which project to target:

{
	"projects": {
		"default": "{your-project-id}"
	}
}

Replace {your-project-id} with your actual project ID, which you can find in the console URL or on the project's settings page.

Creating the function

Cloud Functions require their own package.json inside a functions directory. Two dependencies are mandatory: firebase-admin and firebase-functions. Even if you don't call firebase-admin directly, the deployment will fail without it installed. The engines.node field is also required so Firebase knows which Node.js version to run your function on:

{
	"engines": {
		"node": "12"
	},
	"dependencies": {
		"firebase-admin": "^9.3.0",
		"firebase-functions": "^3.11.0"
	}
}

Before installing these dependencies, add a .gitignore file to keep node_modules out of version control:

node_modules

# ignores a few log files firebase creates when running functions locally
*.log

Then run the install command from the functions directory:

cd functions && npm install

This generates a package-lock.json file—commit it, but you can ignore it for now.

With dependencies in place, create functions/index.js. Each property exported from this module becomes a separate function:

const functions = require('firebase-functions')

module.exports = {
	hello: functions.https.onRequest((req, res) => {
		const subject = req.query.subject || 'World'
		res.send(`Hello ${subject}!`)
	}),
}

This exports a single function named hello. Some developers prefer the alternative export syntax:

exports.hello = functions.https.onRequest((req, res) => {})

Both forms are functionally equivalent; the choice is stylistic. The file doesn't have to be named index.js, but if you rename it, you must update the main field in package.json.

Running locally with the emulator

To test your function before deploying, install the firebase-tools CLI:

npm install --global firebase-tools

Next, authenticate with your Google account:

firebase login

This opens a browser window for login. Once authenticated, start the Firebase emulator from your project directory:

firebase emulators:start

The emulator provides a UI where you can monitor function logs and test your endpoint directly in a browser. You'll see the function output and log entries confirming it executed.

Deploying to production

With the local test passing, attempt a deployment:

firebase deploy

You'll likely encounter an error about billing not being enabled. The error message doesn't explain what to do, but the fix is straightforward: navigate to "Usage and billing" in the Firebase console, open the "Details & settings" tab, and upgrade to the "Blaze Pay as you go" plan. This requires a credit card on file via Google Cloud billing, but Firebase's free tier should cover modest usage before any charges apply.

Once billing is configured, run the deploy command again:

firebase deploy

After the deployment finishes, you'll receive an endpoint URL. Hitting that endpoint in a browser returns your function's output. The sample function also accepts a subject query parameter, so appending ?subject=Bob%20Ross produces a personalized greeting.

Automating deployment with GitHub Actions

Manual deployments from a laptop get tedious. To automate the process, create a workflow file at .github/workflows/deploy.yml:

name: deploy
on:
  push:
    branches:
      - main
jobs:
  main:
    runs-on: ubuntu-latest
    steps:
      - name: ⬇️ Checkout repo
        uses: actions/checkout@v2

      - name: ⎔ Setup node
        uses: actions/setup-node@v1
        with:
          node-version: 12

      - name: 📥 Download deps
        working-directory: functions
        run: npm install

      - name: 🚀 Deploy
        run: npx firebase-tools deploy --token "$FIREBASE_TOKEN"
        env:
          FIREBASE_TOKEN: ${{ secrets.FIREBASE_TOKEN }}

This workflow triggers on pushes to the main branch (or master if that's your default). It performs four steps: clone the repository, install Node v12, install dependencies in the functions directory via npm, and deploy using firebase-tools. The deployment step differs from local usage in three ways: it uses npx instead of a global install, passes a --token flag instead of an interactive login, and reads the token from a FIREBASE_TOKEN environment variable.

Generate a CI token by running:

firebase login:ci

This walks you through another login and outputs a token on completion. Copy it into your GitHub repository's secrets configuration at https://github.com/{your-username}/{your-repo-name}/settings/secrets/actions. With the secret added, push your changes and watch the deployment run automatically from the repository's Actions tab.

The complete working example is available in the finished repo. With this baseline in place, you can explore Cloud Functions' more advanced capabilities—triggers, authentication hooks, and Firestore integrations—without wrestling with setup details.