Introduction to 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.
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.
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:
- Subscription: The client subscribes to push notifications through the Push API and receives an endpoint URL, along with cryptographic keys.
- Send Push Message: The server uses the endpoint URL and keys to send a push message to the push service.
- 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
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.
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.
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.
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.
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.
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.
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);
});
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)
Great introduction. For anyone using Ruby (instead of Node) I recommend this open source library (I am the maintainer): github.com/pushpad/web-push/
Great!
Congratulation! I'm exciting for the next part!
Can you link to part 2 please?
Link to Git Repo ~ Github
Thanks for the informative material, I’m looking forward to the second part
One moment
throw new Error('No key set vapidDetails.publicKey');
moved to file subscription-service.ts