- Published on
Firebase Realtime Database with NestJS
- Authors
- Name
- PatharaNor
Ref. https://github.com/patharanordev/nest-firebase-rltdb
Preparation
Firebase service account
Download service account (*.json
) from Firebase console, it should look like this :
{
"type": "service_account",
"project_id": "YOUR_PROJECT_ID",
"private_key_id": "YOUR_PRIVATE_KEY_ID",
"private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvg.....9aSkqIJxHcom\n-----END PRIVATE KEY-----\n",
"client_email": "firebase-adminsdk-???????@YOUR_PROJECT_ID.iam.gserviceaccount.com",
"client_id": "YOUR_CLIENT_ID",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/firebase-adminsdk-???????%40YOUR_PROJECT_ID.iam.gserviceaccount.com"
}
add project_id
, private_key
, client_email
and your Realtime database URL
to your .env
file.
FIREBASE_PROJECT_ID=YOUR_PROJECT_ID
FIREBASE_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\nMIIEvg.....9aSkqIJxHcom\n-----END PRIVATE KEY-----\n"
FIREBASE_CLIENT_EMAIL=firebase-adminsdk-???????@YOUR_PROJECT_ID.iam.gserviceaccount.com
FIREBASE_REALTIME_DATABASE=YOUR_REALTIME_DATABASE_URL
Accessing to Firebase
In src/main.ts
, initial Firebase admin :
import { NestFactory } from '@nestjs/core'
import { AppModule } from './app.module'
import { ConfigService } from '@nestjs/config'
import * as admin from 'firebase-admin'
import { ServiceAccount } from 'firebase-admin'
async function bootstrap() {
const app = await NestFactory.create(AppModule)
const configService: ConfigService = app.get(ConfigService)
const adminConfig: ServiceAccount = {
projectId: configService.get<string>('FIREBASE_PROJECT_ID'),
privateKey: configService.get<string>('FIREBASE_PRIVATE_KEY'),
clientEmail: configService.get<string>('FIREBASE_CLIENT_EMAIL'),
}
admin.initializeApp({
credential: admin.credential.cert(adminConfig),
databaseURL: configService.get<string>('FIREBASE_REALTIME_DATABASE'),
})
app.enableCors()
await app.listen(3000)
}
bootstrap()
and don't forget adding ConfigModule
to AppModule
in app.module.ts
:
// ...
import { ConfigModule } from '@nestjs/config'
@Module({
imports: [ConfigModule],
// ...
})
export class AppModule {}
Now you can connecting to Firebase, let's create sample service/API. In this case I assume that service is payment service, I will update payment status to the Firebase Realtime database.
Implement update service
I created status service (src/status/
), the service will update result to Firebase Realtime database by reference to user ID, I called id
:
// Ref. src/status.controller.ts
// ...
@Controller()
export class StatusController {
// ...
@Post('/status/:id')
async updateStatus(@Param() params): Promise<string> {
return await this.statusService.updateStatus(params.id)
}
}
In the service, just update payment status and current timestamp to specific path of our Firebase Realtime database {FIREBASE_REALTIME_DATABASE}/{ENV}/{SERVICE_NAME}/{USER_ID}
.
For timestamp, don't forget set timezone in Dockerfile
too :
# Set timezone
ENV TZ=Asia/Bangkok
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
For production, please don't forget changing Firebase Realtime database child path to environment variable.
// Ref. src/status.service.ts
import { Injectable } from '@nestjs/common'
import * as admin from 'firebase-admin'
@Injectable()
export class StatusService {
async updateStatus(id: string): Promise<string> {
const rootRef = admin.database().ref('/')
const result = new Promise<string>((resolve, reject) => {
const timezone = new Date().getTimezoneOffset() * 60000 //offset in milliseconds
const timestamp = new Date(Date.now() - timezone).toISOString().slice(0, -1)
rootRef
.child('dev')
.child('payment')
.child(id)
.set(
{
status: 'payment-success',
timedstamp: timestamp,
},
(err) => {
if (err) {
reject(err.message)
} else {
resolve('Done')
}
}
)
})
return result
}
}
Usage
I prepared Dockerfile.local
and docker-compose.local.yml
to testing on localhost, just run :
docker-compose -f docker-compose.local.yml up --build
In this case the specific Firebase Realtime database path, it is {FIREBASE_REALTIME_DATABASE}/dev/payment/t2
, please refer to image below :
- ENV: dev
- SERVICE_NAME: payment
- USER_ID: t2
To stop the service :
docker-compose -f docker-compose.local.yml down -v
Issues
In case your service cannot connect to Firebase, it shows infinite error message below :
# ... @firebase/database: FIREBASE WARNING: {"code":"app/invalid-credential","message":"Credential implementation provided to initializeApp() via the \"credential\" property failed to fetch a valid Google OAuth2 access token with the following error: \"Error fetching access token: Error while making request: getaddrinfo ENOTFOUND accounts.google.com. Error code: ENOTFOUND\"."} @firebase/database: FIREBASE WARNING: {"code":"app/invalid-credential","message":"Credential implementation provided to initializeApp() via the \"credential\" property failed to fetch a valid Google OAuth2 access token with the following error: \"Error fetching access token: Error while making request: getaddrinfo ENOTFOUND accounts.google.com. Error code: ENOTFOUND\"."} @firebase/database: FIREBASE WARNING: {"code":"app/invalid-credential","message":"Credential implementation provided to initializeApp() via the \"credential\" property failed to fetch a valid Google OAuth2 access token with the following error: \"Error fetching access token: Error while making request: getaddrinfo ENOTFOUND accounts.google.com. Error code: ENOTFOUND\"."}
So
client
side need to set timeout if theclient
journey related to Firebase.