It’s been 5 years since Cloud Functions for Firebase first launched as an easy way to trigger server code based on events in your app or in Firebase services. In this fifth year, thanks in large part to your feedback, we’ve made the biggest improvements yet since launch, both to a new runtime with 2nd gen features, and new features for existing 1st gen functions.
Cloud Functions 2nd gen is still in preview, but today we’re excited to recap the top features and catch you up to the latest updates we’ve released since the first launch earlier this year
Write code that responds to events and invokes functionality exposed by other Firebase features.
In this post we’ll catch you up to the latest new capabilities including less cold starts, faster deploys, and type-safe configuration, all implemented to make it easier to write and deploy faster, safer functions.
A second-generation runtime brings fewer cold starts and new triggers
When a wave of excited users suddenly visit our app, the last thing anyone wants is an awkwardly long pause as new function instances boot up to serve each users’ request. It can be enough to send any developer running to Stack Overflow to find out ways to make it faster. What if we re-think Cloud Functions for Firebase from its very foundations to make cold starts happen way less often?
Cold starts happen when a new function instance is initialized to meet demand.
Cold starts add significant latency to any function request. First gen functions can handle one request per instance. If an app gets consistent traffic to its functions, the Cloud Functions backend will scale up the appropriate number of instances to handle that traffic, and things will be fast, with almost no cold starts. However, as soon as a burst of traffic comes in, say 50 requests above normal happen all at once, each of those 50 requests will be queued and may experience a cold start. Ouch! Now those fifty users think the app is slow.
Concurrency means fewer cold starts
In 2nd-gen functions, each instance of a function can handle multiple requests. We call this concurrency. The default concurrency setting is 80, but it can be set as high as 1000 or as low as 1. This means that the functions backend doesn’t need to cold start nearly as many instances when a burst of traffic comes in!
This is a big deal for performance. Now, a burst of 50 additional requests may not result in any cold starts at all.
In 2nd-gen functions, each instance of a function can handle multiple requests. We call this concurrency.
You can try 2nd-gen functions today. Adoption is easy because you can write 2nd gen functions right alongside your 1st-gen functions code, and deploy them all with a single firebase deploy
command.
New Triggers
Second generation functions will support all the triggers that the 1st gen supports (though we’re still working on a few). However, there are some new ones, too!
- Blocking functions add custom user validation to Firebase Authentication.
- Firebase Alerts add triggers that automatically notify your team when your app has a performance or stability regression.
- Custom Events add custom logic to your installed extensions when they emit custom events.
On top of that, all 2nd-gen events are available through EventArc. That means that you can trigger a Cloud Run container off of the same events! Ever wanted to use Ruby on the backend instead of Node.js? Now you can!
Faster, safer deployment with codebases and no-op deploys
Concurrency may speed up user experience, but what about developer experience? Another common complaint about Cloud Functions has been deployment speed. While we’re always working on making deployments faster in the backend, we identified one big opportunity for improvement. Deployment speed scales with the number of functions deployed, so we’re introducing tooling to help make sure that you only deploy the functions you need to during each deployment.
Codebases making code organization easier
Next, easier management of groups of functions. Firebase now supports splitting your functions into distinct groups, called Codebases. Then, when you deploy, you specify which codebase you want to deploy, and the Firebase CLI will deploy only those functions. This has three main benefits:
- Code isolation. Don’t accidentally modify functions in a different codebase.
- Container isolation. Different codebases can have different package.json files, meaning you can minimize the dependencies required for a group of functions, decreasing cold start time
- Faster deploys. Since the CLI will only look at the functions in the codebase you’re trying to deploy.
We suggest grouping functions into a codebase based on what they do. For example, if you notice that a few of your functions are for user management (maybe a beforeUserCreated
trigger and some Firestore triggers that watch a users
collection), put them in a codebase called “users”. Functions used for an api that powers features in your app (HTTPS or callable) could be a separate codebase called “public-api”. Then, when you modify functions in one codebase, deploy with:
To get started with using codebase, run firebase init functions
on your existing functions directory.
Automated deploy skipping
Codebases works beautifully with a new feature: Automated deploy skipping. In the past, the firebase deploy
command would deploy all of your functions every single time. The CLI would still deploy everything even if only one function changed. We have good news! That’s changing. Now the CLI will only deploy the changed codebases.
How does it work? Under the hood, during the deployment process the Firebase CLI generates a unique hash composed of the files and their contents, the SECRET
versions, and the ENV variables and stores it in the Cloud Functions backend alongside your function. The next time anyone on your team attempts to deploy functions, Firebase CLI will compare the current generated hash with the last deployment. Only changes within a codebase will trigger a deployment.
Portable functions and secure API keys with parameterized configuration
Parameterized config
In the past, firebase functions:config
was the standard way to have configurable environment variables in 1st-gen functions. However, it has some limitations: it’s not meant for secret values, like API keys, and it doesn’t make any guarantees about whether a value is set or what type that value is. Parameterized configuration for first-and-second generation functions solves both of these problems.
Parameters are created with a define
function:
import { defineInt, defineString } from 'firebase-functions/params';
// The keys are either defined in .env or they are created
// via prompt in the CLI before deploying
const minInstancesConfig = defineInt('HELLO_WORLD_MININSTANCES');
const welcomeMessage = defineString('WELCOME_MESSAGE');
Now, when you run firebase deploy
, the CLI will prompt for values for all of the parameters defined in your function code. Once you provide them, it will write them into a .env
file in your project directory that you can check into git so that you don’t need to provide them again next time.
If you’re using a parameter to configure your function, you can pass it in directly:
import { onRequest } from 'functions/v2/https';
import { defineInt, defineString } from 'firebase-functions/params';
const minInstancesConfig = defineInt('HELLO_WORLD_MININSTANCES');
const welcomeMessage = defineString('WELCOME_MESSAGE');
export const helloworld = onRequest(
{ minInstances: minInstancesConfig }, (req, res) => {
});
Type-safe configuration
To access the value of a parameter at runtime, use the .value()
method. The great thing about this method is that it’s type-safe. Because your define()
function specifies the type of the parameter, and the CLI won’t let you deploy without providing a value for the parameter, we can provide specific TypeScript types:
import { onRequest } from 'functions/v2/https';
import { defineInt, defineString } from 'firebase-functions/params';
const minInstancesConfig = defineInt('HELLO_WORLD_MININSTANCES');
const welcomeMessage = defineString('WELCOME_MESSAGE');
export const helloworld = onRequest(
{ minInstances: minInstancesConfig }, (req, res) => {
// .value() will return a string!
res.send(`${welcomeMessage.value()}! I am a function.`);
});
Defining secrets
Additionally, there is a special kind of parameter called a secret parameter, which can contain sensitive values like API keys that shouldn’t be stored in git. Secret parameters let you reference secrets in Cloud Secret Manager, instead of storing them locally, and are defined with the defineSecret()
function, and can only be accessed at runtime by functions you’ve specified are allowed access to them:
import { onRequest } from 'functions/v2/https';
import { defineSecret } from 'firebase-functions/params';
const discordApiKey = defineSecret('DISCORD_API_KEY');
export const discordpost = onRequest(
{ secrets: [discordApiKey] }, (req, res) => {
const apiKey = discordApiKey.value();
// ...
});
Parameterized configuration simplifies function configuration helping keep information secure and type-safe.
A new generation of Cloud Functions
We are 5 years into Cloud Functions for Firebase and we’re incredibly excited about these updates. We hope they make your functions faster for end users, faster to deploy, and faster and safer to develop. Try them now with the v2 getting started guide, or peruse pre-written functions in the functions-samples repository.
Emulator Suite support
All of the features are supported by the Firebase Emulator Suite so that you can try them locally to get a feel for how they work. And, as always, keep letting us know your feedback through the firebase-functions GitHub repository.
We’re working hard to get Cloud Functions gen2 to the finish line, and we look forward to giving more updates in the new year.