Step-by-Step Guide to Setting Up Push Notifications in Node.js: Backend Configuration
Tags: webdev, javascript, programming, tutorial
Published: 2024-06-30 12:15:52

## Introduction to Push Notifications

![Push Notifications](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dsci6p3nz7jfoun03blb.png)

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9dqg4kaduhh8hvq98o1o.png)

### 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gdgxk5ve4pc5lqphh9wh.png)

### 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.

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

#### 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
```

#### Step 3: Initialize Sequelize

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

```typescript
import { Sequelize } from 'sequelize';

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

export default sequelize;
```

#### Step 4: Define the Subscription Model

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

```typescript
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;
```

#### Step 5: Set Up the Service Layer

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

```typescript
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));
  });
};
```

#### Step 6: Create API Routes and Controllers

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

```typescript
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.' });
  }
};
```

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

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

const router = Router();

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

export default router;
```

#### Step 7: Initialize the Server

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

```typescript
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:${PORT}`);
  });
}).catch ((err) => {
  console.error('Unable to connect to the database:', err);
});
```

## 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](https://www.linkedin.com/in/sanjampreetsingh/)

---

Stay tuned for Part 2 - Client Side!