Welcome to the second part of our push notification series! In this article, we'll dive into creating a Nest Js server capable of sending push notifications to our React web application. We'll cover the setup, implementation, and even extend our server to handle multiple tokens and topic notifications.
Table of Contents
- Introduction
- Project Setup
- Project Structure
- Implementing the Server
- Testing the Server
- Connecting with the React Frontend
- Next Steps
- Conclusion
Introduction
In this article, we'll build a NestJS server that can send push notifications to a React web application. We'll use Firebase Cloud Messaging (FCM) as our push notification service. By the end of this tutorial, you'll have a fully functional server capable of sending notifications to single devices, multiple devices, and topic subscribers.
Project Setup
Installing NestJS CLI
First, let's ensure we have the NestJS CLI installed. The CLI helps us create and manage NestJS projects easily.
npm i -g @nestjs/cli
This command installs the NestJS CLI globally on your machine.
Creating a New NestJS Project
Now, let's create a new NestJS project:
nest new push-notification-server
cd push-notification-server
This creates a new NestJS project named "push-notification-server" and navigates into the project directory.
Installing Dependencies
We need to install some additional dependencies for our project:
npm install firebase-admin dotenv @nestjs/swagger
Here's what each package does:
-
firebase-admin
: Allows us to interact with Firebase services, including FCM. -
dotenv
: Helps us manage environment variables. -
@nestjs/swagger
: Provides tools for generating API documentation.
Project Structure
Our project will have the following key files:
-
src/main.ts
: The entry point of our application -
src/app.module.ts
: The root module of our application -
src/app.service.ts
: The service containing our push notification logic -
src/app.controller.ts
: The controller containing our push notification endpoints -
src/dto/notification.dto.ts
: The DTO file containing our data transfer objects
Let's go through each file and implement our push notification server.
Implementing the Server
Main Application File
// src/main.ts
import { NestFactory } from "@nestjs/core";
import { SwaggerModule, DocumentBuilder } from "@nestjs/swagger";
import { AppModule } from "./app.module";
async function bootstrap() {
const app = await NestFactory.create(AppModule);
const config = new DocumentBuilder()
.setTitle("Push Notification API")
.setDescription("The Push Notification API description")
.setVersion("1.0")
.addTag("Push Notification with FCM")
.build();
const document = SwaggerModule.createDocument(app, config);
SwaggerModule.setup("api/docs", app, document);
await app.listen(9000);
}
bootstrap();
Let's break down this file:
- We import necessary modules from NestJS and Swagger.
- The
bootstrap
function is the entry point of our application. - We create a NestJS application instance with
NestFactory.create(AppModule)
. - We set up Swagger for API documentation using
DocumentBuilder
. - We create a Swagger document and set it up at the "/api/docs" endpoint.
- Finally, we start the server on port 9000.
App Module with Firebase Configuration
// src/app.module.ts
import { Module } from "@nestjs/common";
import * as admin from "firebase-admin";
import { config } from "dotenv";
import { AppController } from "./app.controller";
import { AppService } from "./app.service";
config();
@Module({
imports: [],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {
constructor() {
admin.initializeApp({
credential: admin.credential.cert({
projectId: process.env.FIREBASE_PROJECT_ID,
clientEmail: process.env.FIREBASE_CLIENT_EMAIL,
privateKey: process.env.FIREBASE_PRIVATE_KEY?.replace(/\\n/g, "\n"),
}),
});
}
}
This file sets up our main application module:
- We import necessary modules and services.
- The
config()
call loads environment variables from a.env
file. - We define our
AppModule
with its controllers and providers. - In the constructor, we initialize Firebase Admin SDK with credentials from our environment variables.
Note:
Make sure to set up your Firebase project and add the necessary environment variables (FIREBASE_PROJECT_ID, FIREBASE_CLIENT_EMAIL, FIREBASE_PRIVATE_KEY) in a.env
file in your project root. You can refer back to our previous episode of this series to learn how to create your firebase project and retrieve the necessary keys.
App Service
// src/app.service.ts
import { Injectable } from "@nestjs/common";
import * as admin from "firebase-admin";
import {
MultipleDeviceNotificationDto,
NotificationDto,
TopicNotificationDto,
} from "./dto/notification.dto";
@Injectable()
export class AppService {
async sendNotification({ token, title, body, icon }: NotificationDto) {
try {
const response = await admin.messaging().send({
token,
webpush: {
notification: {
title,
body,
icon,
},
},
});
return response;
} catch (error) {
throw error;
}
}
async sendNotificationToMultipleTokens({
tokens,
title,
body,
icon,
}: MultipleDeviceNotificationDto) {
const message = {
notification: {
title,
body,
icon,
},
tokens,
};
try {
const response = await admin.messaging().sendMulticast(message);
console.log("Successfully sent messages:", response);
return {
success: true,
message: `Successfully sent ${response.successCount} messages; ${response.failureCount} failed.`,
};
} catch (error) {
console.log("Error sending messages:", error);
return { success: false, message: "Failed to send notifications" };
}
}
async sendTopicNotification({
topic,
title,
body,
icon,
}: TopicNotificationDto) {
const message = {
notification: {
title,
body,
icon,
},
topic,
};
try {
const response = await admin.messaging().send(message);
console.log("Successfully sent message:", response);
return { success: true, message: "Topic notification sent successfully" };
} catch (error) {
console.log("Error sending message:", error);
return { success: false, message: "Failed to send topic notification" };
}
}
}
This service contains the core logic for sending push notifications. Let's break down each method:
-
sendNotification
:- Purpose: Sends a notification to a single device token.
- Parameters: Takes a
NotificationDto
object with token, title, body, and icon. - Process: Uses Firebase Admin SDK to send a message to the specified token.
-
sendNotificationToMultipleTokens
:- Purpose: Sends a notification to multiple device tokens.
- Parameters: Takes a
MultipleDeviceNotificationDto
object with tokens array, title, body, and icon. - Process: Uses Firebase Admin SDK's
sendMulticast
method to send to multiple tokens at once.
-
sendTopicNotification
:- Purpose: Sends a notification to all devices subscribed to a specific topic.
- Parameters: Takes a
TopicNotificationDto
object with topic, title, body, and icon. - Process: Uses Firebase Admin SDK to send a message to the specified topic.
Each method constructs a message object with the notification details and uses the Firebase Admin SDK to send the message. The methods return an object indicating the success or failure of the operation.
App Controller
// src/app.controller.ts
import { Controller, Post, Body } from "@nestjs/common";
import { AppService } from "./app.service";
import { ApiTags, ApiOperation, ApiResponse } from "@nestjs/swagger";
import {
MultipleDeviceNotificationDto,
TopicNotificationDto,
} from "./dto/notification.dto";
@ApiTags("notifications")
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@Post("send-notification")
@ApiOperation({ summary: "Send a push notification to a single device" })
@ApiResponse({ status: 200, description: "Notification sent successfully" })
async sendNotification(
@Body() body: { token: string; title: string; body: string; icon: string }
) {
return this.appService.sendNotification({
token: body.token,
title: body.title,
body: body.body,
icon: body.icon,
});
}
@Post("send-multiple-notifications")
@ApiOperation({ summary: "Send push notifications to multiple devices" })
@ApiResponse({ status: 200, description: "Notifications sent successfully" })
async sendMultipleNotifications(@Body() body: MultipleDeviceNotificationDto) {
return this.appService.sendNotificationToMultipleTokens({
tokens: body.tokens,
title: body.title,
body: body.body,
icon: body.icon,
});
}
@Post("send-topic-notification")
@ApiOperation({ summary: "Send a push notification to a topic" })
@ApiResponse({
status: 200,
description: "Topic notification sent successfully",
})
async sendTopicNotification(@Body() body: TopicNotificationDto) {
return this.appService.sendTopicNotification({
topic: body.topic,
title: body.title,
body: body.body,
icon: body.icon,
});
}
}
This controller sets up three endpoints:
-
/send-notification
:- HTTP Method: POST
- Purpose: Sends a notification to a single device
- Body: Expects token, title, body, and icon
-
/send-multiple-notifications
:- HTTP Method: POST
- Purpose: Sends notifications to multiple devices
- Body: Expects an array of tokens, title, body, and icon
-
/send-topic-notification
:- HTTP Method: POST
- Purpose: Sends a notification to all devices subscribed to a topic
- Body: Expects topic, title, body, and icon
Each endpoint is decorated with Swagger annotations for clear API documentation. The @ApiTags
, @ApiOperation
, and @ApiResponse
decorators provide metadata for the Swagger UI.
Notification DTO
// src/dto/notification.dto.ts
import { ApiProperty, ApiPropertyOptional, PickType } from "@nestjs/swagger";
export class NotificationDto {
@ApiProperty({
type: String,
description: "Client device token",
})
token: string;
@ApiProperty({
type: String,
description: "Notification Title",
})
title: string;
@ApiProperty({
type: String,
description: "Notification Body",
})
body: string;
@ApiPropertyOptional({
type: String,
description: "Notification Icon / Logo",
})
icon: string;
}
export class MultipleDeviceNotificationDto extends PickType(NotificationDto, [
"title",
"body",
"icon",
]) {
@ApiProperty({
type: String,
description: "Clients device token",
})
tokens: string[];
}
export class TopicNotificationDto extends PickType(NotificationDto, [
"title",
"body",
"icon",
]) {
@ApiProperty({
type: String,
description: "Subscription topic to send to",
})
topic: string;
}
This file defines our Data Transfer Objects (DTOs):
-
NotificationDto
: Base DTO for single device notifications -
MultipleDeviceNotificationDto
: ExtendsNotificationDto
for multiple device notifications -
TopicNotificationDto
: ExtendsNotificationDto
for topic notifications
The @ApiProperty
and @ApiPropertyOptional
decorators provide metadata for Swagger documentation.
Testing the Server
With this setup, you can now run your NestJS server:
npm run start:dev
You can use the Swagger UI at http://localhost:9000/api/docs
to test your endpoints. To send a notification to your React app:
- Get the FCM token from your React app (as implemented in the previous article)
- Use the
/send-notification
endpoint, providing the token, title, body and icon of the notification
Connecting with the React Frontend
To use this NestJS server with the React frontend we built in the previous article:
- When your React app receives an FCM token, send it to this NestJS server and store it (you might want to create an endpoint for this).
- Use the stored token(s) when calling the notification endpoints.
Next Steps
- Implement token storage in a database for persistent device targeting.
- Add authentication to your NestJS server to secure the notification endpoints.
- Implement more advanced notification features like actions, images, etc.
Conclusion
This NestJS server provides a robust backend for sending push notifications to your web application. By extending it with multiple token support and topic notifications, you now have a scalable solution for reaching your users with timely updates and information.
Stay Updated and Connected
To ensure you don't miss any part of this series and to connect with me for more in-depth discussions on Software Development (Web, Server, Mobile or Scraping / Automation), push notifications, and other exciting tech topics, follow me on:
Your engagement and feedback drive this series forward. I'm excited to continue this journey with you and help you master the art of push notifications across web and mobile platforms.
Don't hesitate to reach out with questions, suggestions, or your own experiences with push notifications.
You can find below the working source code for this article.
Source Code 🚀
Top comments (0)