Updated At: 2024-08-22T13:32:41.342Z

How to Create a Push Notification API using Firebase Cloud Messaging (FCM) Using Express.JS and Sequilize

Push notification is one of crucial element in a modern Apps because it one of the method to engage with user. So, this article I will love to share my experience creating an API to to send a push notification using FCM. To make this works, atleast there is three minimum components that we need to have. The three components are:

  1. Firebase SDK, especially as FCM Admin;
  2. Database consist of two tables, which are:
    • Devices,
    • Notifications (Optional), it depends on your needs. This table will be used to save a notification you've send;
  3. Node.Js (it interchangeable depends on your preference, you can use python, GO, etc).

After having all the components we can start by creating a Firebase SDK or FCM Admin to access it.

Firebase SDK/ FCM Admin

To begin you can open Firebase Console and create a new project (if you don't have one). After that you can go to project settings, service account then generate new private key. This key will be used to connect your code to create the API with FCM server. We will back at this later.

How FCM Works

Firebase Cloud Messaging is one of the Firebase's service that allow any apps to send a push notification. There are two sides that need to exist, client side and server side. With this case client side will act as a receive which will receive the notification, and server side is the one that will send the notification. But both of them can't be stand alone because client will need to send their FCMToken to server side. Let me explain in point:

  • So, basically to send a push notification to any device, Firebase need a Firebase Token of the client/ target. To generate the FCM Token, client need to also install or initialize a firebase app with the same project as the server side (admin). When there is a prompt to validate access for receive a push notification, FCM Initialize will generate a FCMToken that will be used as their deviceID.
  • In server side (FCM Admin), after they initialize FCM Admin, they finally be able to send a push notification to the client by calling FCM API/ function with parameter of client's FCmToken and the message.
  • To make things easier, the database will take an important part. So, for example whenever device initialize the app for the first time or if the app detect that notification permission is still off, they will generate a permission for notification and if user aggree on that, client will generate an unique FCMToken and send the FCMToken to the database.
  • After that, whenever there is and API call to server push notification they will check if the FCM already exist in the database or not, if exist, they will send the notification and client will receive it.

Database

Next is we need to understand how is the data models that we need to have. The important part is devices tables. It consist of atleast 1 atribute and several other atributes that could be added depends on your needs.

  1. fcmToken: will store client/ device FCM Token
  2. userID | UUID (opt): foreign key from users table, depend on your case
  3. active | boolean (opt): status wheter the token is active or not
  4. timestamps

Server Side/ Backend

After have all the item above, we can start code and create a backend to accomodate our needs. Main goals of this is to create an API to store FCMToken from client and create an API to send a push notification. To accomplish this let's start with creating an Node.JS Apps, assumming that you already have Node.JS installed on your machine.

Instalation

mkdir push-notif
cd push-notifi

After that, we can init the Node Package Manager (NPM)

npm init

Then we can start init express.js since we will use this as the framework

npm install express

Then we can start add sequelize

npm install --save sequelize
npm install --save pg pg-hstore # Postgres

Finally we can install firebase-admin SDK

npm install --save firebase-admin

Setup

With this case we are using MVC as the base but you can use another design pattern you like, I will not explain about MVC in this article. So, our code will be consit of two main components which is to initialize the Firebase SDK and to create the API to send a notification.

FCM Admin Setup

const admin = require('firebase-admin')
const serviceAccount = process.env.SERVICE_ACC

// Initialize Firebase Admin SDK
if (!admin.apps.length) {
  // Check if Firebase Admin SDK is not already initialized
  admin.initializeApp({
    credential: admin.credential.cert(serviceAccount)
  })
}

// Export the admin object for use in other files
module.exports = admin

Within that code, what we do is initialize the firebase-admin-sdk as admin and call initializeApp with params of serviceAccount. We can have the service Account from key we generated earlier. Basically, credential.cert(//params) will receive either object or path-to-file to the .JSON where key saved.

Method to store the FCMToken

const Validator = require('fastest-validator')
const { Devices } = require('../models') //Import from model

const uuid = require('uuid')
const val = new Validator()

exports.create = async (req, res) => {
  const { userId, FCMToken } = req.body //get the payload from request body

  const schema = {
    userId: 'uuid',
    FCMToken: 'string'
  } //schema to validate if the format is right

  const checkDevice = await Devices.findAll({
    where: { userId, FCMToken }
  }) // get data from devices to check if useId and FCMToken is exist

  if (checkDevice.length > 0) {
    return res.status(200).json({ message: 'Already Exist', checkDevice })
  } // validate, if exist, we will skip the saving process

  const validate = val.validate(req.body, schema) //validate the data

  if (validate.length) {
    return res.status(400).json(validate)
  } // if data is not valid, we will not save to database

  try {
    req.body.id = uuid.v4()
    const device = await Devices.create(req.body) //save to database
    res.status(201).json({
      data: device
    })
  } catch (error) {
    res.status(400).json(error)
  }
}

Method to send notification

// Import necessary module
const { Devices } = require('../models')
const admin = require('../admin-firebase') //import admin from admin that used for initialize the apps

// send notification to device
exports.sendNotification = async (req, res) => {
  const {
    title, // notification title (ex: New Message)
    body, // notification body (ex: Hi John, howdy!)
    to //userID
  } = req.body

  // try to get the FCMToken from database where userId == to
  const checkDevice = await Devices.findAll({
    attributes: ['FCMToken'],
    where: { userId: to }
  })

  const fcmTokens = checkDevice.map(
    (checkDevice) => checkDevice.dataValues.FCMToken
  ) // map the FCMToken from {} to [], thiw will be used to send multicast

  // check if device not registered
  if (checkDevice.length < 1) {
    // if not registered, return 204 and stop the process
    return res
      .status(204)
      .json({ message: 'User Not Activate Push Notification', checkDevice })
  }

  // send a notification
  try {
    // message payload as the firebase paramaters
    const message = {
      notification: {
        title,
        body
      },
      // fcmTokens using array because it multicast and send the message to multiple device with same ID
      tokens: fcmTokens
    }
    // console.log(fcmTokens)
    admin
      .messaging()
      .sendMulticast(message)
      .then((response) => {
        console.log('Successfully sent message:', response)
        res.status(200).send({ message: 'Success' })
      })
      .catch((error) => {
        console.log('Error sending message:', error)
        res.status(500).send({ error: error.message })
      })
  } catch (error) {
    res.status(500).send({ error: error.message })
  }
}

That's It!

With the example code above you can start add the security factor, or another necessary code that you needed.