DEV Community

Sanjampreet Singh
Sanjampreet Singh

Posted on

Step-by-Step Guide to Setting Up Push Notifications in Node.js: Backend Configuration

Introduction to Push Notifications

Push Notifications

Push notifications are a powerful way to keep users engaged by delivering timely and relevant information directly to their devices. Unlike traditional pull mechanisms where the client requests information from the server, push notifications allow the server to send updates to the client without the client explicitly requesting it.

In this three-part series, we'll guide you through setting up push notifications from scratch using Node.js, without relying on third-party services. In this first part, we'll focus on setting up the backend to handle push notifications.

Understanding the Architecture

Before diving into the code, let's understand the architecture and key components involved in implementing push notifications.

High level architecture

What is VAPID ?

Voluntary Application Server Identification(VAPID) is a method for identifying your application server to push services(e.g., Google, Mozilla) without requiring a third - party authentication service.VAPID provides a way to include your server's identity in the push message, allowing push services to validate and associate the message with your server.

VAPID Keys

How Does the HTTP Push Protocol Work ?

The HTTP Push Protocol is an extension of HTTP / 2 that allows servers to send unsolicited responses(push messages) to clients.Here’s a simplified flow of how it works:

  1. Subscription: The client subscribes to push notifications through the Push API and receives an endpoint URL, along with cryptographic keys.
  2. Send Push Message: The server uses the endpoint URL and keys to send a push message to the push service.
  3. Delivery: The push service delivers the message to the client’s browser, which then displays the notification using the Service Worker API.

Setting Up the Backend

Let's set up a Node.js backend to handle push notifications. We'll use Express for our server, Sequelize for interacting with an SQLite database, and web - push for sending notifications.

Step - by - Step Guide to Implementing Push Notifications

Step 1: Initialize the Project

First, create a new Node.js project and install the necessary dependencies.



npm init -y
npm install express sequelize sqlite3 web-push dotenv
npm install --save-dev typescript @types/node @types/express


Enter fullscreen mode Exit fullscreen mode

Step 2: Set Up Environment Variables

Create a .env file to store your VAPID keys:



VAPID_PUBLIC_KEY=your_public_key
VAPID_PRIVATE_KEY=your_private_key


Enter fullscreen mode Exit fullscreen mode

Step 3: Initialize Sequelize

Create a src/models/index.ts file to initialize Sequelize and connect to the SQLite database.



import { Sequelize } from 'sequelize';

const sequelize = new Sequelize({
  dialect: 'sqlite',
  storage: './data.db'
});

export default sequelize;


Enter fullscreen mode Exit fullscreen mode

Step 4: Define the Subscription Model

Create a src/models/subscription.ts file to define the subscription model.



import { DataTypes, Model } from 'sequelize';
import sequelize from './index';

class Subscription extends Model {
  public id!: number;
  public endpoint!: string;
  public p256dh!: string;
  public auth!: string;
}

Subscription.init({
  id: {
    type: DataTypes.INTEGER,
    autoIncrement: true,
    primaryKey: true
  },
  endpoint: {
    type: DataTypes.STRING,
    allowNull: false
  },
  p256dh: {
    type: DataTypes.STRING,
    allowNull: false
  },
  auth: {
    type: DataTypes.STRING,
    allowNull: false
  }
}, {
  sequelize,
  tableName: 'subscriptions'
});

export default Subscription;


Enter fullscreen mode Exit fullscreen mode

Step 5: Set Up the Service Layer

Create a src/services/subscriptionService.ts file to handle subscription and notification logic.



import Subscription from '../models/subscription';
import webPush from 'web-push';

// Configure VAPID keys
const vapidKeys = {
  publicKey: process.env.VAPID_PUBLIC_KEY!,
  privateKey: process.env.VAPID_PRIVATE_KEY!
};

webPush.setVapidDetails(
  'mailto:your-email@example.com',
  vapidKeys.publicKey,
  vapidKeys.privateKey
);

export const saveSubscription = async (subscription: any): Promise<void> => {
  await Subscription.create({
    endpoint: subscription.endpoint,
    p256dh: subscription.keys.p256dh,
    auth: subscription.keys.auth
  });
};

export const sendNotification = async (title: string, body: string, image: string): Promise<void> => {
  const subscriptions = await Subscription.findAll();
  subscriptions.forEach((subscription) => {
    const sub = {
      endpoint: subscription.endpoint,
      keys: {
        p256dh: subscription.p256dh,
        auth: subscription.auth
      }
    };
    const payload = JSON.stringify({
      notification: {
        title,
        body,
        image,
      },
    });
    webPush.sendNotification(sub, payload)
      .catch(error => console.error('Error sending notification:', error));
  });
};


Enter fullscreen mode Exit fullscreen mode

Step 6: Create API Routes and Controllers

Create src/api/controllers/subscriptionController.ts for handling API requests.



import { Request, Response } from 'express';
import { saveSubscription, sendNotification } from '../../services/subscriptionService';

export const subscribe = async (req: Request, res: Response) => {
  try {
    const subscription = req.body;
    await saveSubscription(subscription);
    res.status(201).json({ message: 'Subscription added successfully.' });
  } catch (error) {
    res.status(500).json({ message: 'Failed to subscribe.' });
  }
};

export const pushNotification = async (req: Request, res: Response) => {
  try {
    const { title, body, image } = req.body;
    await sendNotification(title, body, image);
    res.status(200).json({ message: 'Notification sent successfully.' });
  } catch (error) {
    res.status(500).json({ message: 'Failed to send notification.' });
  }
};


Enter fullscreen mode Exit fullscreen mode

Create src/api/routes/subscriptionRoutes.ts to define the API routes.



import { Router } from 'express';
import { subscribe, pushNotification } from '../controllers/subscriptionController';

const router = Router();

router.post('/subscribe', subscribe);
router.post('/push', pushNotification);

export default router;

Enter fullscreen mode Exit fullscreen mode




Step 7: Initialize the Server

Create src/index.ts to set up the Express server and initialize the database.



import express from 'express';
import cors from 'cors';
import helmet from 'helmet';
import subscriptionRoutes from './api/routes/subscriptionRoutes';
import sequelize from './models/index';
import dotenv from 'dotenv';

dotenv.config(); // Load environment variables from .env

const app = express();
const PORT = process.env.PORT || 3000;

app.use(cors());
app.use(helmet());
app.use(express.json());

app.use('/api', subscriptionRoutes);

sequelize.sync().then(() => {
app.listen(PORT, () => {
console.log(Server is running on http://localhost:</span><span class="p">${</span><span class="nx">PORT</span><span class="p">}</span><span class="s2">);
});
}).catch ((err) => {
console.error('Unable to connect to the database:', err);
});

Enter fullscreen mode Exit fullscreen mode




Summary

In this first part of our series, we've set up the backend for push notifications using Node.js. We've covered the basics of push notifications, the architecture, and provided a step-by-step guide to implementing the backend. In the next part, we'll dive into setting up the frontend to handle push notifications and subscribe users.


If you like what you read, consider connecting with me on LinkedIn


Stay tuned for Part 2 - Client Side!

Top comments (6)

Collapse
 
collimarco profile image
Marco Colli

Great introduction. For anyone using Ruby (instead of Node) I recommend this open source library (I am the maintainer): github.com/pushpad/web-push/

Collapse
 
sanjampreetsingh profile image
Sanjampreet Singh

Link to Git Repo ~ Github

Collapse
 
fullzero5 profile image
FullZero

Thanks for the informative material, I’m looking forward to the second part
One moment
throw new Error('No key set vapidDetails.publicKey');

require('dotenv').config(); 
Enter fullscreen mode Exit fullscreen mode

moved to file subscription-service.ts

Collapse
 
robertofonsecabc profile image
Roberto Fonseca

Congratulation! I'm exciting for the next part!

Collapse
 
steve_haran_4a1719c084a0c profile image
Steve Haran

Can you link to part 2 please?

Collapse
 
robertofonsecabc profile image
Roberto Fonseca

Great!