In our last post, we learned how to add notifications to an android app.  We covered how to generate tokens for users, send them to a remote server and receive messages, however, we didn't engage in server-side programming in that very post to keep it open for all kind of server frameworks.

We will discuss Node.js framework in this post, and learn how to handle the tokens received from a client and then use those tokens to send notifications back to the clients. However, the setup discussed in this post is client independent and can be used for all firebase clients -- Android, Web or iOS.

---

To get started with Node.js, we first need to install the required dependencies. We will be using express framework as our default node.js framework and BodyParser module to parse body from post requests. MongoDB will serve as our primary datastore for client tokens. We will also be making use of request module for requesting external urls, get or post. The following command will install all four modules and save them in our project's package.json.

sudo npm i express body-parser mongodb request --save

After the modules are correctly installed, we need to create an instance of express as app and add the bodyparser middleware to it. The example below does that.

'use strict'
const app = require('express')(),
request = require('request'),
mongo = require('mongodb'),
bodyParser = require('body-parser')
app.use(bodyParser.json())
app.use(bodyParser.urlencoded({
extended: false
}))
const MongoClient = mongo.MongoClient
const url = 'mongodb://localhost:27017/myDatabase'
view raw fcm1.js hosted with ❤ by GitHub

We have also created an instance of MongoDB client to connect to our database (myDatabase). We will give MongoClient the location of our database using an URL. The format of the URL is pretty straightforward. It begins with the protocol name, MongoDB (like HTTP or RTSP), followed by the location address and port number of the MongoDB database. The address should be localhost or 127.0.0.1 if the database (mongod daemon) is running on the same server (machine). If running in a different location, the IP address of that server is required as the address. The default port number on which MongoDB runs is 27017. If using a different port number, we need to specify that. The last part of the URL is the name of the database, which in our case is myDatabase.

app.get('/notifications', (req, res) => {
res.sendFile(__dirname + '/notifcenter.html')
})
app.post('/store', (req, res) => {
MongoClient.connect(url, (err, db) => {
if (err) throw err
else {
db.collection('tokens').insertOne(req.body, (err, body) => {
if (err) throw err
res.sendStatus(200)
})
}
db.close()
})
})
view raw fcm2.js hosted with ❤ by GitHub

We will declare an endpoint where we will send our tokens. Let's add a POST endpoint, store, which will get our token out of the request body and using MongoClient, store it in tokens collection of myDatabase. We try to connect to the database with the url we declared earlier. Once connected, the connect method gives database (db) in need as well as an exception object as callback. If no exception occurs, we will insert the token into the tokens collection using collection's insertOne method. In the example above, the post body parsed from request, req.body, is passed directly as the data to be inserted as it contains token only. If there are more key-value pairs in it, we can get token value out of the body and then put it as insertion data.

let tokenValue = req.body.token
let data = { token: tokenValue }

If data is inserted without any error, we will again get a callback where we send response ok with status code 200. After that, just close the database.

Okay, so we are done with storing tokens. Now, we use these tokens to send notifications. Let's declare a new function, sendNotification that accepts message data.

const sendNotifications = (data) => {
const dataString = JSON.stringify(data)
const headers = {
'Authorization': 'key=<your firebase legacy server key>',
'Content-Type': 'application/json',
'Content-Length': dataString.length
}
const options = {
uri: 'https://fcm.googleapis.com/fcm/send',
method: 'POST',
headers: headers,
json: data
}
request(options, function (err, res, body) {
if (err) throw err
else console.log(body)
})
}
view raw fcm3.js hosted with ❤ by GitHub

The data sendNotifications function accepts is pure JSON. We need to change it into its string equivalent using JSON.stringify(...) to be able to send it to FCM endpoint.

const headers = {
 'Authorization': 'key=<your firebase legacy server key>',
 'Content-Type': 'application/json',
 'Content-Length': dataString.length 
}

FCM endpoint, other than data, also requires our legacy server key as one of the request headers. We can get the legacy server key from Firebase Console > Settings (the icon on top-right) > Cloud Messaging. Notice the headers object declared above has an Authorization key. This very key holds the server key we just collected from Firebase Console. However, there's a catch. Most people working with authorization key for the first time often ignore including key= with the server key. Don't! Also, we add Content-Type and Content-Length headers in the headers object just to be sure that FCM server can parse the header and thus, the key. Both headers are self-explanatory.


Once we have the header ready, we send a POST request to FCM server with the header and message data using request and for that, we need a properly structured options object containing FCM endpoint (URI), headers, the method of request, POST, and of course, JSON (data). If having an issue understanding, go to fcm3.js and look at the code, it's very straightforward.

const sendToTopics = (msg, title, topic, response) => {
const data = {
"data": {
"body": msg,
"title": title
}
}
data['to'] = '/topics/' + topic
sendNotifications(data)
response.sendStatus(200)
}
view raw fcm4.js hosted with ❤ by GitHub

Okay, we have our base notification sending function ready. We now need to identify the notification type and prepare data message accordingly. We first start with topic-based notification. Topic-based notifications are used in combination with GCMPubSub, a tool that lets users subscribe to certain topics and then receive notifications only for those topics. We'll talk about GCMPubSub some other day. The example (fcm4.js) above details about the data object we need to create. You'll notice that the data object contains the obvious body and title. Feel free to add more data to it.

The only difference between topic-based notification and normal notification is the target audience. Topic-based notification expects a to key with the topic (s) of interest. Once done adding topics to the data object, just call the sendNotifications(..) and then send a response (status code 200, ok) back to the NotificationCenter (we will talk about it in a moment).

While topic based notifications expect topics, normal notifications require tokens. The data object remains the same as topic-based, but instead of to, we need registration_ids to hold the token array.

const sendToAll = (msg, title, regIdArray, response) => {
const data = {
"data": {
"body": msg,
"title": title
}
}
const folds = regIdArray.length % 1000
for (let i = 0; i < folds; i++) {
let start = i * 1000,
end = (i + 1) * 1000
data['registration_ids'] = regIdArray.slice(start, end).map((item) => {
return item['token']
})
sendNotifications(data)
}
response.sendStatus(200)
}
view raw fcm5.js hosted with ❤ by GitHub

The sendToAll(...) function we use to send token based notifications also accepts regIdArray, other than the usual message, title, and response. The regIdArray is an array of all token objects we have stored in the database. Look below (fcm6.js) for a hint. Since it's an array of objects and not a string, we need to map it from object to plain string array. We will use Array's map method for that. You will notice in the example above that we tried to batch the registration ids (tokens) in small arrays of 1000. The reason behind it is we are not allowed to send a notification to more than 1000 ids at once. We use Array's slice method to create smaller arrays of length 1000.

data['registration_ids'] = regIdArray.slice(start, end).map((item) => { 
                return item['token'] 
})

The following piece of code does that, assuming the key for token value is token. A for loop for every 1000 registration ids runs batching them and calling sendNotification(...) function every time. Finally, an ok response is sent back.

app.post('/notify', (req, res) => {
let msg = req.body.message,
title = req.body.title,
type = req.body.type,
topic = req.body.topic
if (type === 'topic') {
sendToTopics(msg, title, topic, res)
} else {
MongoClient.connect(url, function (err, db) {
if (err) throw err
else {
db.collection('tokens').find({}).toArray((err, docs) => {
sendToAll(msg, title, docs, res)
})
}
db.close()
})
}
})
app.listen(8000)
view raw fcm6.js hosted with ❤ by GitHub

Now, it's time to identify the notification type. We will do that in the notify endpoint we declare for the Notification Center (where the notification message is prepared). Look at the HTML page declared below.  We have two radio buttons. One for sendToAll, the other for Topic-based. We also have a select (drop-down) menu to choose the topic in case Topic-based radio button is selected. The form will pick the appropriate data as per the selection and send it to our notify endpoint (POST). We will get our message, title, notification type and if available, topic. 

If the type is topic, we call sendToTopics(...) passing message, title, topic and response object. If not, we connect to our database using MongoClient and call sendToAll(...). The parameters passed to it will remain same except one. The topic is replaced with tokens array we receive from the database call (find?).

<!DOCTYPE html>
<html>
<head>
<title>Notification Center</title>
<style>
body {
text-align: center;
}
h1 {
font-size: 40px;
font-family: Verdana, Geneva, Tahoma, sans-serif;
color: #444;
}
input[type=text] {
width: 300px;
margin: 0 auto;
display: block;
}
.border {
padding: 15px;
border-color: #7a7a7a;
border-style: solid;
font-family: Verdana, Geneva, Tahoma, sans-serif;
border-width: 2px;
border-radius: 3px;
}
textarea {
width: 300px;
margin: 0 auto;
height: 200px;
display: block;
margin-top: 20px;
}
button {
background: #444;
margin-top: 20px;
color: white;
display: block;
margin: 20px auto;
}
input[type=radio] {
margin: 20px 10px;
}
select {
display: block;
width: 200px;
display: none;
margin: 10px auto;
}
</style>
</head>
<body>
<h1>Notification Center</h1>
<form method="post" action="http://localhost:8000/notify">
<input class="border" type="text" name="title" placeholder="notiification title">
<textarea class="border" name="message" placeholder="message"></textarea>
<input type="radio" name="type" value="all">Send to all
<input type="radio" name="type" value="topic">Send to topic subscribers only
<select class="border" name="topic">
<option value="topic1">Topic 1</option>
<option value="topic2">Topic 2</option>
<option value="topic3">Topic 3</option>
</select>
<button class="border">Send Notification</button>
</form>
<script>
var all = document.getElementsByName('type')[0]
var topic = document.getElementsByName('type')[1]
var select = document.getElementsByTagName('select')[0]
console.log(all, topic, select)
topic.onclick = function () {
select.style.display = 'block'
all.checked = false
}
all.onclick = function () {
select.style.display = 'none'
topic.checked = false
}
</script>
</body>
</html>
view raw notifcenter.html hosted with ❤ by GitHub

That's it. We have successfully created two endpoints, one for storing tokens and the other for sending notification using a topic or tokens. We have also created a Notification center (notifcenter.html) using HTML, CSS and a little javascript to let us create notification body and select its type. Notification center is available as a get request at '/notifications' (see fcm1.js). Feel free to share your issues or experience in comments below.