This is a plugin for Nx that adds support for deploying Cloud Functions for Firebase.
From version 2.0.0 this plugin only supports cloud functions v2, if you want v1 support use version 1.2.2.
- Features
- Install
- Description
- Prerequisites
- Helper Functions
- Folder Structure
- Cloud cache
- Logger
- Executors
- Auto alias support
- Support for multiple environments
- Esbuild for faster builds
- Detect changes and only deploy changed functions
- No longer export all functions in a index.ts file, but deploy each function individually for smaller bundles
- Configurable deploy options
- Deploy with Node 14/16/18/20 and esm
- Cloud functions v2 support
- Deploy rules and indexes
- Execute scripts locally
- Read env file and copy to clipboard
- Cloud cache support
- Run emulators locally
- AWS SAM: build, deploy and watch logs for lambda functions
pnpm i -D nx-cloud-functions-deployer
This plugin adds a deploy
executor that will build and deploy your cloud functions.
It uses esbuild to bundle your functions and then uses the firebase-tools to deploy them.
- You will need to have the firebase-tools installed. Either globally or locally in your project. If you install it globally you have to set
packageManager
option toglobal
.
pnpm i -D firebase-tools
If you want to use Cloud cache or run scripts you will also need tsx installed.
pnpm i -D tsx
You need to import the helper functions from nx-cloud-functions-deployer
. This will allow you to configure your functions and make the functions stronger typed.
For schedule functions options.schedule is required.
import { onSchedule } from 'nx-cloud-functions-deployer';
export default onSchedule(
(context) => {
console.log('daily', context);
},
{
schedule: 'every day 00:00',
},
);
Auth triggers have the following functions: onAuthCreate
, onAuthDeleted
,beforeAuthCreate
and beforeAuthDeleted
.
import { onAuthCreate } from 'nx-cloud-functions-deployer';
export default onAuthCreate(({ uid }) => {
console.log('New user created: uid', uid);
});
Database triggers have the following functions: onValueCreated
, onValueDeleted
, onValueUpdated
and onValueWritten
. You must always add the ref
for each database function.
import { onValueCreated } from 'nx-cloud-functions-deployer';
export default onValueCreated(
({ data }) => {
console.log('New gmail user created:', data.val());
},
{
ref: '/user/{data.key=*@gmail.com}',
},
);
When you use the onWritten
, onCreated
, onUpdated
or onDeleted
helper functions for firestore. The data will automatically convert the snapshot to
{
...documentSnapshot.data(),
id: documentSnapshot.id,
}
// shared lib
import type { CoreData } from 'nx-cloud-functions-deployer';
export interface UserData extends CoreData {
email: string;
}
import { onCall } from 'nx-cloud-functions-deployer';
import type { UserData } from '@my-project/shared';
export default onCreated<UserData>(({ data }) => {
console.log(`User ${data.email} created`);
});
onCall and onRequest can give even better typing with frontend. By importing a interface from a shared file.
// shared lib
export type MyFunctions = {
my_function_name: [
{
message: string; // request data
},
{
response: boolean; // response data
},
];
};
import { onCall } from 'nx-cloud-functions-deployer';
import type { MyFunctions } from '@my-project/shared';
export default onCall<MyFunctions, 'my_function_name'>(({ data }) => {
console.log(data); // { message: string }
return { response: true };
});
To include assets and external dependencies you can use the assets
and external
options.
External will install the dependencies in the function folder and add them to the package.json. This is useful for dependencies that do not work with bundle.
Assets will copy the files from src/assets to the dist folder (right next to index.js)
import { onCall } from 'nx-cloud-functions-deployer';
export default onCall(
({ data }) => {
return { response: true };
},
{
assets: ['test.png'],
external: ['sharp'],
keepNames: true,
},
);
Also note that if you use sharp, you need keepNames: true. See documentation
You cannot have comments, variables or numeric separators in the options section. If the options section is invalid it will skip the options and deploy as default.
All these lines in the options are not allowed:
import { onCall } from 'nx-cloud-functions-deployer';
const region = 'europe-west1';
const memory = '2GB';
export default onCall(
(data, context) => {
return { response: true };
},
{
region,
timeoutSeconds: 1_800,
memory: memory,
// a random comment
},
);
See the example for a better understanding of the folder structure.
It is recommend to have the following folder structure, but it is not required.
├── your-nx-project
│ ├──src
│ │ ├── controllers
│ │ │ ├── https
│ │ │ │ ├── callable
│ │ │ │ ├── request
│ │ │ ├── database
│ │ │ ├── firestore
│ │ │ ├── storage
| | | ├── auth
│ │ │ ├── pubsub
│ │ │ │ ├── schedule
│ │ │ │ ├── topic
The folders in controllers will different deployment types:
request
- HTTP requestscallable
- Callable functionsfirestore
- Cloud Firestore triggersdatabase
- Cloud Firestore triggersschedule
- Scheduled functionsstorage
- Cloud Storage triggersauth
- Auth triggers
The default function names will be the path from the api/callable/database/scheduler
folder to the file. For example, the function controllers/api/stripe/webhook_endpoint.ts
will be deployed as stripe_webhook_endpoint
.
The firestore structure is a little different. It is recommend the folder structure to match to structure in the firestore. For example, if you have a firestore structure like this:
├── users # collection
│ ├── uid # a user document
│ │ ├── notifications # a sub collection
│ │ │ ├── notificationId # a notification document in the sub collection
Then you would have the following folder structure:
├── firestore
│ ├── users
│ │ ├── [uid]
│ │ │ ├── created.ts # will be called every time a user document is created.
│ │ │ ├── updated.ts # will be called every time a user document is updated.
│ │ │ ├── deleted.ts # will be called every time a user document is deleted.
│ │ │ ├── notifications
│ │ │ │ ├── [notificationId]
│ │ │ │ │ ├── created.ts # will be called every time a notification document is created.
The default function name for database/firestore functions will omit [id]
. Example: controllers/database/users/[id]/created.ts
will be deployed as users_created
.
To customize the folder structure, change the functionsDirectory
in options.
If you change the structure you have to specify the document
for firestore functions and ref
for database functions.
Example:
// controllers/firestore/my-custom-user-created-file.ts
import { onCreated } from 'nx-cloud-functions-deployer';
import type { UserData } from '@my-project/shared';
export default onCreated<UserData>(
({ data }) => {
console.log(`User ${data.email} created`);
},
{
functionName: 'custom_function_name',
document: 'users/{id}',
},
);
The plugin will detect changes on the deployed functions locally. But it is also possible to cache the changes for the deployed functions on your own server. To do this create a file in the project directory called functions-cache.ts
.
The file needs to export two function fetch
and update
which will be called by the plugin. Note you will also get the environments, so you can use process.env, if you want to hide production secrets for fetching/updating the cache.
import type {
FunctionsCacheFetch,
FunctionsCacheUpdate,
} from 'nx-cloud-functions-deployer';
export const fetch: FunctionsCacheFetch = async ({ flavor }) => {
// fetch the cache from the cloud
};
export const update: FunctionsCacheUpdate = async ({
flavor,
newFunctionsCache,
}) => {
// update the cache in the cloud
};
See the example for how to setup cloud cache with jsonbin
If you want to see metric for each function (like opentelemetry or sentry) , add a logger.ts file in the src folder (see example). The logger will be build and added for each function.
...
"targets": {
"deploy": {
"executor": "nx-cloud-functions-deployer:deploy",
"options": {
"flavors": {
"development": "firebase-project-development-id",
"production": "firebase-project-production-id"
}
}
},
Option | Description | Default | Alias |
---|---|---|---|
flavors |
A object of the flavors to use, the key is the flavor name and value is the firebase project id. | required | |
flavor |
The flavor to use, default will be the first key in the flavors object |
||
production |
If true, the flavor will be 'production' if flavor is not defined |
false |
prod |
development |
If true, the flavor will be 'development' if flavor is not defined |
false |
dev |
outputDirectory |
The output directory. | dist/<relative-path-to-project> |
outDir |
envFiles |
the key is the flavor name and value is path to the env file, default is .env.${flavor} |
||
tsconfig |
The tsconfig file to use for the build in the project directory. | tsconfig.json |
tsconfig |
region |
The default region to deploy the functions, if it is not set in the deploy file. See Cloud Functions Locations. | us-central1 |
location |
silent |
Whether to suppress all logs. | false |
s |
verbose |
Whether to run the command with verbose logging. | false |
v |
concurrency |
The number of functions to deploy in parallel | 5 |
c |
envString |
Stringify version of the environment. This is useful when you want to deploy using CI/CD. | undefined | ciEnv |
only |
Only deploy the given function names separated by comma | undefined | o |
force |
Force deploy all functions, even if no files changed | false |
f |
packageManager |
The package manager to use for deploying with firebase-tools. Either: pnpm , npm , yarn or global . |
pnpm |
pm |
dryRun |
If true, then it will only build the function and not deploy them. | false |
d , dry |
functionsDirectory |
Relative path from the project root to the functions directory. | src/controllers |
inputDirectory |
nodeVersion |
The node version to use for the functions. | 20 |
node |
minify |
Whether to minify the functions | true |
pnpm nx deploy functions --flavor production
pnpm nx deploy functions --development
pnpm nx deploy functions --development --only my_function,my_other_function --f
# will deploy only the functions my_function and my_other_function
# and deploy them even if no files have changed
The plugin also provide support to run scripts locally. The plugin will run any files in the scripts
directory. The files needs to export default a function.
import { firestore } from '$configs/firestore';
import type { ScriptFunction } from 'nx-cloud-functions-deployer';
export default (async ({ prompt }) => {
console.log('prompt', prompt);
const { uid } = await prompt<{ uid: string }>({
type: 'input',
name: 'uid',
message: 'Enter the uid',
validate: (value) => {
if (value.length === 20) {
return true;
}
return 'The uid must be 20 characters long';
},
});
const documentSnap = await firestore.collection('users').doc(uid).get();
if (!documentSnap.exists) {
throw new Error('User not found');
}
return { id: documentSnap.id, ...documentSnap.data() };
}) satisfies ScriptFunction;
You can also add a script-config.{flavor}.ts
file in the project root. In these files you can execute code before before running a script.
To make firebase work locally see example.
...
"targets": {
"script": {
"executor": "nx-cloud-functions-deployer:script",
"options": {
"flavors": {
"development": "firebase-project-development-id",
"production": "firebase-project-production-id"
}
}
},
Option | Description | Default | Alias |
---|---|---|---|
flavors |
A object of the flavors to use, the key is the flavor name and value is the firebase project id. | required | |
flavor |
The flavor to use, default will be the first key in the flavors object |
||
production |
If true, the flavor will be 'production' if not defined |
false |
prod |
development |
If true, the flavor will be 'development' if not defined |
false |
dev |
envFiles |
the key is the flavor name and value is path to the env file, default is .env.${flavor} |
||
tsconfig |
The tsconfig file to use for the script in the project directory. | tsconfig.json |
tsconfig |
silent |
Whether to suppress all logs. | false |
s |
verbose |
Whether to run the command with verbose logging. | false |
v |
scriptsRoot |
Relative path from the project root to the scripts directory. | scripts |
|
runPrevious |
Rerun the last executed script. | false |
p |
script |
The name of the script to run. If not set, it will prompt you to select from a list. | undefined | file |
pnpm nx script functions --production
pnpm nx script functions --flavor development
pnpm nx script functions --development -p
# will run the last executed script in development
The plugin provide support to delete unused function that are not in the project anymore. The plugin will delete any functions that are not in the functions
directory.
...
"targets": {
"delete-unused": {
"executor": "nx-cloud-functions-deployer:delete",
"options": {
"flavors": {
"development": "firebase-project-development-id",
"production": "firebase-project-production-id"
}
}
},
Option | Description | Default | Alias |
---|---|---|---|
flavors |
A object of the flavors to use, the key is the flavor name and value is the firebase project id. | required | |
flavor |
The flavor to use, default will be the first key in the flavors object |
||
production |
If true, the flavor will be 'production' if not defined |
false |
prod |
development |
If true, the flavor will be 'development' if not defined |
false |
dev |
envFiles |
the key is the flavor name and value is path to the env file, default is .env.${flavor} |
||
tsconfig |
The tsconfig file to use for the script in the project directory. | tsconfig.json |
tsconfig |
silent |
Whether to suppress all logs. | false |
s |
verbose |
Whether to run the command with verbose logging. | false |
v |
deleteAll |
Whether to delete all functions even if they are in your project. | false |
The plugin provide support to emulate functions locally. The plugin will emulate any functions that are in the functions
directory.
...
"targets": {
"emulate": {
"executor": "nx-cloud-functions-deployer:emulate",
"options": {
"flavors": {
"development": "firebase-project-development-id",
"production": "firebase-project-production-id"
},
"only": ["functions"],
"packageManager": "global",
"minify": false
}
},
Option | Description | Default | Alias |
---|---|---|---|
flavors |
A object of the flavors to use, the key is the flavor name and value is the firebase project id. | required | |
flavor |
The flavor to use, default will be the first key in the flavors object |
||
envFiles |
the key is the flavor name and value is path to the env file, default is .env.${flavor} |
||
tsconfig |
The tsconfig file to use for the script in the project directory. | tsconfig.json |
tsconfig |
silent |
Whether to suppress all logs. | false |
s |
verbose |
Whether to run the command with verbose logging. | false |
v |
only |
Only emulate the given services separated by comma "auth", "functions","firestore","hosting","pubsub","storage" | undefined | o |
minify |
Whether to minify the functions | true |
This will read your .env file in your selected flavor, copy it to the clipboard and print it in console. This is so you can use envString
when you deploy your functions in CI.
...
"targets": {
"delete-unused": {
"executor": "nx-cloud-functions-deployer:read-env",
"options": {
"flavors": {
"development": "firebase-project-development-id",
"production": "firebase-project-production-id"
}
}
},
Option | Description | Default | Alias |
---|---|---|---|
flavors |
A object of the flavors to use, the key is the flavor name and value is the firebase project id. | required | |
flavor |
The flavor to use, default will be the first key in the flavors object |
||
envFiles |
the key is the flavor name and value is path to the env file, default is .env.${flavor} |
||
silent |
Whether to suppress all logs. | false |
s |
verbose |
Whether to run the command with verbose logging. | false |
v |
See the example here.
There is now also support for aws sam to deploy and watch logs. You need the SAM CLI installed to use this feature. See the example here.
...
"deploy": {
"executor": "nx-cloud-functions-deployer:sam-deploy",
"options": {
"flavors": {
"development": "example-dev-test"
},
"bucket": "test"
}
},
"logs": {
"executor": "nx-cloud-functions-deployer:sam-logs",
"options": {
"flavors": {
"development": "example-dev-test"
},
"name": "ExampleFunction",
"tail": true
}
}