Firebase
Through this brick, you can obtain a Nest application already configured to run Firebase Cloud Functions. This ready-to-use configuration allows you to easily integrate Firebase's serverless functionalities within a Nest application, simplifying the development of scalable and responsive solutions.
Get Started
To create a new Firebase application, use the following Devmy CLI command:
devmy generate application firebase
Variables
You can configure the initial application with the following parameters:
- Application name: The name of the application
Advantages
Configure Environments
During the application installation, the DevmyCLI
will automatically log in to Firebase (opens in a new tab) and scan all visible projects.
Subsequently, you will be prompted to configure the development, staging, and production environments with the previously
scanned projects or choose to skip the configuration for now.
If you skip the configuration, you will need to manually set up the
environments in the .firebaserc
file.
Vitest
Tests are executed using Vitest, a fast and lightweight testing framework designed for Vite projects. Vitest provides a testing experience similar to Jest but with enhanced speed due to its optimized integration with Vite. This allows for efficient and rapid execution of unit tests, integration tests, and snapshots.
Firebase Emulator
The Firebase Emulator Suite is a set of advanced tools provided by Firebase to enable local development and testing of Firebase services. It allows developers to simulate various Firebase products on their local machines, ensuring that applications can be developed, tested, and debugged without affecting the production environment. Key features of the Firebase Emulator Suite include:
Local Emulation: Emulates Firebase services such as Firestore, Realtime Database, Authentication, Hosting, Functions, and more. Real-time Feedback: Provides immediate feedback and logs for operations, facilitating rapid development and debugging. Integration Testing: Supports complex integration testing by simulating real-world interactions between different Firebase services. Security Rules Testing: Allows testing of security rules for Firestore and Realtime Database locally to ensure that the rules work as intended before deploying them to production. Function Testing: Enables local invocation and testing of Cloud Functions, making it easier to develop and test serverless functions without deploying them.
Export/Import Data
Firestore allows exporting and importing data for backup, migration, or
restoration purposes. The export operation generates a file that includes the
data and metadata of the entire collection or a subset of it. This export file
is in a binary format optimized for Firestore, meaning it is not readable by
other tools or applications, making it essential to handle these backups with
care. When starting the application in development mode, the data
folder will
be imported automatically.
It is crucial to be careful when modifying data imported and exported on Firestore, as some end-to-end (E2E) tests may depend on that data and thus fail.
Functions
A Firebase Cloud Function is a serverless function that runs in response to events triggered by Firebase services, HTTPS requests, or other Google Cloud events. It allows you to execute backend code without managing servers, enabling you to perform tasks such as handling user authentication, processing real-time database changes, sending notifications, and more. Cloud Functions scale automatically based on demand, making it easy to build and maintain scalable, event-driven applications.
OnCall vs OnRequest
In Firebase Cloud Functions, onCall
and onRequest
are two different ways to define functions that respond to events:
onCall
Functions
- Type: Triggered by a direct call from a Firebase client app.
- Usage: Typically used for scenarios where you need to execute backend code in response to a function call from the client-side code (e.g., mobile or web apps).
- Data Handling: Automatically handles data serialization and deserialization, making it simpler to pass complex data structures.
- Authentication: The function automatically receives the Firebase Authentication context, making it easy to verify the user's identity and access permissions.
- Example Use Case: Handling user registration or updating user profiles where you want to call a backend function directly from your client-side application.
onRequest
Functions
- Type: Triggered by HTTP requests.
- Usage: Ideal for situations where you need to handle traditional HTTP requests or build REST APIs. This function can respond to various HTTP methods such as GET, POST, PUT, DELETE, etc.
- Data Handling: Requires manual handling of request and response objects. You’ll need to parse request data and format responses yourself.
- Authentication: Authentication and authorization must be managed manually, often by inspecting request headers or tokens.
- Example Use Case: Building a custom REST API endpoint or handling webhook requests.
The brick includes wrappers to facilitate development for both types of
Firebase Cloud Functions, onCall
and onRequest
. However, onCall
functions are preferred due to their seamless integration with Firebase
client apps, offering automatic data handling and authentication context,
which simplifies development and enhances security.
Usage
To create a function, you need to define a Nest module and a handler
class that implements the appropriate interface:
OnCallHandler
for onCall
functions and OnRequestHandler
for onRequest
functions.
The handler
class will manage the function logic and respond to the calls, while the Nest module will integrate and manage
the execution context of the functions within the NestJS framework.
import { Module } from "@nestjs/common";
import { FireormModule } from "nestjs-fireorm";
import { Cat } from "./entities";
import CatHandler from "./handlers/cat.handler";
import CatRequestHandler from "./handlers/cat-request.handler";
import { CatMapper, CatService } from "./services";
@Module({
imports: [FireormModule.forFeature([Cat])],
providers: [CatService, CatMapper, CatHandler, CatRequestHandler],
})
export default class CatsModule {}
import { Injectable } from "@nestjs/common";
import { OnCallHandler } from "../../firebase-functions-adapters";
import { CatDTO } from "../dtos";
import { CatService } from "../services";
@Injectable()
class CatHandler implements OnCallHandler<void> {
constructor(private readonly catService: CatService) {}
handle(): Promise<Array<CatDTO>> {
return this.catService.findAll();
}
}
export default CatHandler;
To complete the process, you need to export the function using the utilities provided by the brick, namely
createNestOnCall
for onCall
functions and createNestOnRequest
for onRequest
functions.
After defining the function within the Nest module, it is crucial to export it properly from the src/main.ts
file so that it
can be registered and used as part of the Firebase application.
export const catOnCall = createNestOnCall(
AppModule,
() => import("../cats.module"),
() => import("../handlers/cat.handler")
);
Testing
Testing Firebase Cloud Functions in a NestJS Application
To effectively test Firebase Cloud Functions in a NestJS application, it is essential to set up a comprehensive testing environment.
This involves leveraging the @nestjs/testing
module to create a testing module that includes all necessary providers and dependencies
required for the function.
Start by importing the necessary modules and dependencies, including firebase-functions-test
for Firebase functions testing and vitest
for running tests. In your testing setup, create a NestJS application instance using Test.createTestingModule
. This module should register
the handlers and services your function relies on. For example, if you're testing a catOnCall
function, make sure to include the
CatHandler
, CatRequestHandler
, and a mock implementation of the CatService
.
The firebaseFunctionsTest
library is used to initialize Firebase functions testing. After creating the Nest application, obtain the instance
of the service you're mocking (e.g., CatService
) and wrap your Cloud Function for testing using a utility function such as wrapNestOnCallForTest
.
In the test cases, mock the necessary service methods and define the expected behavior. For instance, mock a method like findAll
in
the CatService
to return a predefined set of data. Then, invoke the Cloud Function and assert that the response matches the expected output.
Here is a template for setting up and testing a Cloud Function:
import { INestApplication } from "@nestjs/common";
import { Test } from "@nestjs/testing";
import firebaseFunctionsTest from "firebase-functions-test";
import { FeaturesList } from "firebase-functions-test/lib/features";
import { beforeAll, describe, expect, Mocked, test, vi } from "vitest";
import {
wrapNestOnCallForTest,
WrappedNestOnCall,
} from "../../firebase-functions-adapters";
import { CatDTO } from "../dtos";
import CatHandler from "../handlers/cat.handler";
import CatRequestHandler from "../handlers/cat-request.handler";
import { CatService } from "../services";
import { catOnCall } from "./cat.function";
describe("Cats OnCall", () => {
let app: INestApplication;
let firebase: FeaturesList;
let catOnCallFunction: WrappedNestOnCall;
let catService: Mocked<CatService>;
beforeAll(async () => {
const moduleRef = await Test.createTestingModule({
providers: [
CatHandler,
CatRequestHandler,
{ provide: CatService, useValue: { findAll: vi.fn() } },
],
}).compile();
firebase = firebaseFunctionsTest();
app = moduleRef.createNestApplication();
catService = app.get(CatService);
catOnCallFunction = wrapNestOnCallForTest(firebase, app, catOnCall);
});
test("should call cat function", async () => {
catService.findAll.mockResolvedValue([
new CatDTO({ id: "123", name: "Pallino" }),
]);
const actual = await catOnCallFunction({});
expect(actual).toEqual([{ id: "123", name: "Pallino" }]);
});
});
Addons
firebase/hosting
Both during the creation phase of the firebase application and afterwards, it will be possible to add hosting configuration.
The addon will take care of installing the necessary dependencies and configurations so that it is ready to use immediately.