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.



