DEV Community

Piyush Kumar
Piyush Kumar

Posted on

Push Notifications in React Native + Node.js: The Complete Setup Guide (Part 1)

A developer's guide to Firebase Cloud Messaging — from zero to production in under 3 minutes


What is FCM and Why Should You Care?

Firebase Cloud Messaging (FCM) is Google's free service for sending push notifications to Android and iOS devices. The best part? It's completely free — unlimited messages, no credit card required, ever.

Before we dive in, here's why you shouldn't build your own notification system:

Your DIY Solution With FCM
❌ Can't deliver when app is closed ✅ OS handles delivery
❌ Drains battery with persistent connections ✅ Uses OS push channel
❌ Complex iOS APNs setup ✅ FCM abstracts it
❌ Need to maintain infrastructure ✅ Google's global network

How It Works: The 30-Second Explanation

Your backend never talks directly to users' phones. Instead:

Your Backend  →  FCM Servers (Google)  →  User's Device
   (sends)          (routes)                (receives)
Enter fullscreen mode Exit fullscreen mode

FCM uses a device token — a unique string representing one app install on one device — to route messages to the right phone.


The Architecture

Here's what you're building:

┌─────────────────────────────────────────────────────────┐
│                   Your Firebase Project                  │
│                                                           │
│  ┌──────────────────┐      ┌──────────────────────┐     │
│  │  Frontend (App)   │      │   Backend (Node.js)   │     │
│  │  React Native     │      │   Express + Database  │     │
│  │                   │      │                       │     │
│  │  Uses:            │      │  Uses:                │     │
│  │  • google-        │      │  • Service Account    │     │
│  │    services.json  │      │    JSON (in .env)     │     │
│  │  • @react-native- │      │  • firebase-admin     │     │
│  │    firebase/      │      │    SDK                │     │
│  │    messaging      │      │                       │     │
│  │                   │      │                       │     │
│  │  Job:             │      │  Job:                 │     │
│  │  • Get token      │      │  • Send push to       │     │
│  │  • Request        │      │    token via FCM      │     │
│  │    permission     │      │    API                │     │
│  │  • Receive push   │      │                       │     │
│  └──────────────────┘      └──────────────────────┘     │
└─────────────────────────────────────────────────────────┘
                   ↓                    ↓
            ┌──────────────────────────────┐
            │      FCM Servers (Google)     │
            │   Routes to correct device    │
            └──────────────────────────────┘
                          ↓
                    User's Phone
Enter fullscreen mode Exit fullscreen mode

Step 1: Firebase Console Setup (5 minutes)

Create Your Project

  1. Go to console.firebase.google.com
  2. Click "Add project"
  3. Give it a name (e.g., my-awesome-app)

Add Android App

  1. Inside your project → Add app → Android
  2. Enter your package name (e.g., com.myapp)
  3. Download google-services.json
  4. Place it at android/app/google-services.json in your React Native project

Get Backend Credentials

  1. Project Settings → Service Accounts
  2. Click "Generate new private key"
  3. Downloads a JSON file — this is your admin credential

⚠️ Security Alert: Never commit this file to git. Store it as an environment variable.


Step 2: Backend Setup (Node.js)

Install Firebase Admin SDK

npm install firebase-admin
Enter fullscreen mode Exit fullscreen mode

Initialize Firebase

Create config/firebase.js:

const admin = require('firebase-admin');

const serviceAccount = JSON.parse(process.env.FIREBASE_SERVICE_ACCOUNT);

admin.initializeApp({
    credential: admin.credential.cert(serviceAccount)
});

module.exports = admin;
Enter fullscreen mode Exit fullscreen mode

Create Push Notification Service

Create services/pushNotification.js:

const admin = require('../config/firebase');

async function sendPushNotification(deviceToken, title, body, data = {}) {
    try {
        const message = {
            token: deviceToken,
            notification: {
                title: title,
                body: body
            },
            data: data,  // Custom key-value pairs
            android: {
                priority: 'high'  // Wakes up the device
            }
        };

        const response = await admin.messaging().send(message);
        console.log('Push sent successfully:', response);
        return response;
    } catch (error) {
        console.error('Error sending push:', error);

        // Handle expired/invalid tokens
        if (error.code === 'messaging/registration-token-not-registered') {
            // Remove token from database
            console.log('Token is invalid, should be removed from DB');
        }
        throw error;
    }
}

module.exports = { sendPushNotification };
Enter fullscreen mode Exit fullscreen mode

Environment Variables

Create/update your .env:

# Paste the entire service account JSON as a single-line string
FIREBASE_SERVICE_ACCOUNT={"type":"service_account","project_id":"your-project-id",...}
Enter fullscreen mode Exit fullscreen mode

Step 3: Database Setup

Add a column to store device tokens:

-- For PostgreSQL
ALTER TABLE users ADD COLUMN fcm_token TEXT;

-- For MySQL
ALTER TABLE users ADD COLUMN fcm_token VARCHAR(255);
Enter fullscreen mode Exit fullscreen mode

Important: If you need multi-device support (same user, multiple phones), create a separate table:

CREATE TABLE device_tokens (
    id SERIAL PRIMARY KEY,
    user_id INTEGER REFERENCES users(id),
    fcm_token TEXT NOT NULL,
    device_name VARCHAR(100),
    created_at TIMESTAMP DEFAULT NOW(),
    UNIQUE(user_id, fcm_token)
);
Enter fullscreen mode Exit fullscreen mode

Step 4: API Endpoint to Save Token

Create an endpoint to receive tokens from your app:

// routes/user.js
const express = require('express');
const router = express.Router();

// Assuming you have authentication middleware
router.put('/token', authenticateUser, async (req, res) => {
    try {
        const { fcm_token } = req.body;
        const userId = req.user.id;  // From auth middleware

        // Update user's FCM token
        await db.query(
            'UPDATE users SET fcm_token = $1 WHERE id = $2',
            [fcm_token, userId]
        );

        res.json({ success: true, message: 'Token saved successfully' });
    } catch (error) {
        console.error('Error saving FCM token:', error);
        res.status(500).json({ error: 'Failed to save token' });
    }
});

module.exports = router;
Enter fullscreen mode Exit fullscreen mode

Understanding Device Tokens

A device token looks like this:

eExKj8mTR4pQ:APA91bH...very-long-alphanumeric-string
Enter fullscreen mode Exit fullscreen mode

Key Facts:

  • Unique per device + per app install
  • Changes when: app is reinstalled, device is reset, app data is cleared
  • Same device, same app, but reinstalled = new token

Token Lifecycle

Event Token Changes?
App update ❌ No
App uninstall + reinstall ✅ Yes
User clears app data ✅ Yes
Device factory reset ✅ Yes
User switches phones ✅ Yes (new device)

The Complete Flow Diagram

Here's how everything connects:

┌─────────────────────────────────────────────────────────────┐
│                    REGISTRATION FLOW                         │
└─────────────────────────────────────────────────────────────┘

[User's Phone]              [Your Backend]          [Database]
      │                           │                      │
      │─── App opens ────────────►│                      │
      │    Firebase SDK runs      │                      │
      │    Generates token        │                      │
      │                           │                      │
      │─── Login/Signup ─────────►│                      │
      │                           │                      │
      │─── Send FCM token ───────►│                      │
      │    PUT /api/user/token    │                      │
      │                           │                      │
      │                           │─── Save token ──────►│
      │                           │    UPDATE users      │
      │                           │    SET fcm_token     │
      │                           │                      │
      │◄─── Success response ─────│                      │


┌─────────────────────────────────────────────────────────────┐
│                   NOTIFICATION FLOW                          │
└─────────────────────────────────────────────────────────────┘

[Your Backend]          [Database]        [FCM]         [User's Phone]
      │                      │              │                  │
      │─── Event occurs ────►│              │                  │
      │    (new message,     │              │                  │
      │     order update,    │              │                  │
      │     etc.)            │              │                  │
      │                      │              │                  │
      │─── Get user token ──►│              │                  │
      │    SELECT fcm_token  │              │                  │
      │                      │              │                  │
      │◄─── Return token ────│              │                  │
      │                      │              │                  │
      │─── Send push ───────────────────────►│                  │
      │    admin.messaging()                │                  │
      │    .send({ token,                   │                  │
      │            title,                    │                  │
      │            body })                   │                  │
      │                                      │                  │
      │                                      │─── Route to ────►│
      │                                      │    device        │
      │                                      │                  │
      │                                      │                  │
      │                                      │   [Notification  │
      │                                      │    appears on    │
      │                                      │    lock screen]  │
Enter fullscreen mode Exit fullscreen mode

What's in Part 2?

In the next article, we'll cover:

Frontend implementation — Getting the token in React Native

Handling notifications — When app is open, background, or closed

Token refresh logic — Keeping tokens up-to-date

Testing strategies — How to test notifications locally

Production checklist — Everything you need before going live


Quick Reference: Is FCM Really Free?

Service Cost
FCM push notifications ✅ Free (unlimited)
Firebase project (Spark plan) ✅ Free
Firebase Admin SDK ✅ Free
React Native Firebase package ✅ Free (open source)
Your server hosting 💰 You pay for hosting
Your database 💰 You pay for database

FCM has been free since Google acquired Firebase in 2014. No message limits, ever.


Common Mistakes to Avoid

❌ Committing service account JSON to Git

Fix: Add to .gitignore, use environment variables

❌ Not handling token expiration

Fix: Catch messaging/registration-token-not-registered errors and remove stale tokens

❌ Forgetting permission requests

Fix: Always request notification permission on Android 13+ and iOS

❌ Testing only with app open

Fix: Test all three states: foreground, background, and killed


Ready to Implement?

You now have:

  • Firebase project configured
  • Backend initialized with admin SDK
  • Database ready to store tokens
  • API endpoint to receive tokens

Next step: Part 2 will show you the React Native frontend code to complete the integration.


Series Navigation:

  • Part 1: Concepts & Setup (you are here)
  • Part 2: Frontend Implementation & Testing (coming soon)

Found this helpful?

Follow me for Part 2, where we'll implement the complete React Native frontend and handle all notification scenarios.

Tags: #react-native #nodejs #fcm #push-notifications #firebase #mobile-development

Top comments (0)