npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@flusys/nestjs-notification

v4.1.1

Published

Notification module with real-time Socket.IO support

Downloads

719

Readme

@flusys/nestjs-notification

Real-time notification system for NestJS — WebSocket via Socket.IO, persistent PostgreSQL storage, read/unread tracking, company broadcasting, and a cross-module adapter for dependency-free integration.

npm version License: MIT NestJS TypeScript Node.js


Table of Contents


Overview

@flusys/nestjs-notification delivers notifications both in real-time (Socket.IO WebSocket) and persistently (PostgreSQL). Unread notifications survive disconnections and are delivered on the next connection. The NOTIFICATION_ADAPTER allows auth, email, and other modules to send notifications without importing this package directly.


Features

  • Persistent storage — Notifications stored in PostgreSQL with read/unread tracking
  • Real-time WebSocket — Socket.IO gateway at /notifications namespace
  • JWT authentication on WebSocket — Token verified on connection handshake
  • Read trackingisRead + readAt timestamp per notification
  • Mark as read — Single notification or bulk "mark all as read"
  • Unread count — Fast count query for notification badge
  • Company scoping — Filter by company in multi-company setups
  • Broadcast to company — Emit to all users in a company Socket.IO room
  • Online presence — Check if a user is currently connected
  • Cross-module adapterNOTIFICATION_ADAPTER for sending from any module
  • Configurable realtime — Disable Socket.IO gateway when not needed

Compatibility

| Package | Version | |---------|---------| | @flusys/nestjs-core | ^4.0.0 | | @flusys/nestjs-shared | ^4.0.0 | | @nestjs/websockets | ^11.0.0 | | @nestjs/platform-socket.io | ^11.0.0 | | socket.io | ^4.0.0 | | jsonwebtoken | ^9.0.0 | | Node.js | >= 18.x |


Installation

npm install @flusys/nestjs-notification @flusys/nestjs-shared @flusys/nestjs-core
npm install @nestjs/platform-socket.io socket.io

Quick Start

With Realtime Enabled

import { Module } from '@nestjs/common';
import { NotificationModule } from '@flusys/nestjs-notification';

@Module({
  imports: [
    NotificationModule.forRoot({
      global: true,
      includeController: true,
      bootstrapAppConfig: {
        databaseMode: 'single',
        enableCompanyFeature: false,
      },
      config: {
        enableRealtime: true,
        jwtSecret: process.env.JWT_SECRET,
        defaultDatabaseConfig: {
          type: 'postgres',
          host: process.env.DB_HOST,
          port: Number(process.env.DB_PORT ?? 5432),
          username: process.env.DB_USER,
          password: process.env.DB_PASSWORD,
          database: process.env.DB_NAME,
        },
      },
    }),
  ],
})
export class AppModule {}

Using NOTIFICATION_ADAPTER in Another Module

// In auth.service.ts — sends a notification after login
import { NOTIFICATION_ADAPTER } from '@flusys/nestjs-shared/interfaces';
import { INotificationAdapter } from '@flusys/nestjs-shared/interfaces';

@Injectable()
export class AuthenticationService {
  constructor(
    @Optional() @Inject(NOTIFICATION_ADAPTER)
    private readonly notificationAdapter?: INotificationAdapter,
  ) {}

  async afterLogin(userId: string): Promise<void> {
    await this.notificationAdapter?.send({
      userId,
      title: 'New Login',
      message: 'Your account was accessed from a new device.',
      type: 'warning',
    });
  }
}

Module Registration

forRoot (Sync)

NotificationModule.forRoot({
  global?: boolean;
  includeController?: boolean;       // Default: true
  bootstrapAppConfig?: {
    databaseMode: 'single' | 'multi-tenant';
    enableCompanyFeature: boolean;
  };
  config?: {
    enableRealtime?: boolean;        // Default: true — registers Socket.IO gateway
    jwtSecret?: string;              // Required for WebSocket JWT authentication
    defaultDatabaseConfig?: IDatabaseConfig;
    tenants?: ITenantDatabaseConfig[];
  };
})

Behavior:

  • enableRealtime: falseNotificationGateway is NOT registered (no WebSocket)
  • includeController: false → No HTTP endpoints registered
  • global: true → Exports available application-wide

forRootAsync (Factory)

import { ConfigService } from '@nestjs/config';

NotificationModule.forRootAsync({
  global: true,
  includeController: true,
  bootstrapAppConfig: {
    databaseMode: 'single',
    enableCompanyFeature: true,
  },
  imports: [ConfigModule],
  useFactory: (configService: ConfigService) => ({
    enableRealtime: true,
    jwtSecret: configService.get('JWT_SECRET'),
    defaultDatabaseConfig: {
      type: 'postgres',
      host: configService.get('DB_HOST'),
      port: configService.get<number>('DB_PORT'),
      username: configService.get('DB_USER'),
      password: configService.get('DB_PASSWORD'),
      database: configService.get('DB_NAME'),
    },
  }),
  inject: [ConfigService],
})

Exported services:

  • NotificationConfigService
  • NotificationDataSourceProvider
  • NotificationService
  • NotificationHelperService
  • NotificationGateway (when enableRealtime: true)
  • NOTIFICATION_ADAPTER

Configuration Reference

interface INotificationModuleConfig extends IDataSourceServiceOptions {
  /** Enable Socket.IO WebSocket gateway (default: true) */
  enableRealtime?: boolean;

  /** JWT secret for WebSocket connection authentication */
  jwtSecret?: string;
}

Feature Toggles

| Feature | Config | Default | Effect | |---------|--------|---------|--------| | WebSocket gateway | enableRealtime: true | true | Registers NotificationGateway on /notifications namespace | | Company scoping | enableCompanyFeature: true | false | Uses NotificationWithCompany entity; adds company room broadcasting | | Multi-tenant | databaseMode: 'multi-tenant' | 'single' | Per-tenant DataSource connections |


WebSocket Gateway

The gateway runs on the /notifications Socket.IO namespace. JWT authentication is required on connection.

Client Connection

Browser (JavaScript):

import { io } from 'socket.io-client';

const socket = io('http://localhost:2002/notifications', {
  auth: { token: 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...' },
  transports: ['websocket'],
});

socket.on('connect', () => {
  console.log('Connected to notification gateway');
});

socket.on('notification', (data) => {
  console.log('New notification:', data);
  // { id, title, message, type, data, createdAt }
});

socket.on('disconnect', () => {
  console.log('Disconnected');
});

Angular service:

import { io, Socket } from 'socket.io-client';

@Injectable({ providedIn: 'root' })
export class NotificationSocketService {
  private socket: Socket;

  connect(token: string): void {
    this.socket = io(`${environment.apiUrl}/notifications`, {
      auth: { token: `Bearer ${token}` },
    });

    this.socket.on('notification', (data) => {
      this.handleNewNotification(data);
    });
  }

  disconnect(): void {
    this.socket?.disconnect();
  }
}

WebSocket Events

Server → Client (emitted by the gateway):

| Event | Payload | Description | |-------|---------|-------------| | notification | INotificationPayload | New notification delivered in real-time | | notification:count | { count: number } | Updated unread count |

Client → Server (handled by the gateway):

| Event | Payload | Description | |-------|---------|-------------| | join:company | { companyId: string } | Join a company room for broadcast notifications | | leave:company | { companyId: string } | Leave a company room | | ping | — | Keepalive |


API Endpoints

All endpoints use POST and require JWT authentication.

Notifications — POST /notification/*

| Endpoint | Auth | Description | |----------|------|-------------| | POST /notification/get-all | JWT | Get current user's notifications | | POST /notification/get-unread-count | JWT | Get unread notification count | | POST /notification/mark-read | JWT | Mark a single notification as read | | POST /notification/mark-all-read | JWT | Mark all notifications as read | | POST /notification/delete | JWT | Delete a notification | | POST /notification/get/:id | JWT | Get a notification by ID |

Admin — POST /notification/admin/*

| Endpoint | Permission | Description | |----------|-----------|-------------| | POST /notification/admin/send | notification.create | Send notification to specific user(s) | | POST /notification/admin/broadcast | notification.create | Broadcast to all users in a company | | POST /notification/admin/get-all | notification.read | List all notifications (all users) |

Send notification request:

POST /notification/admin/send
{
  "userId": "uuid",
  "title": "Order Shipped",
  "message": "Your order #12345 has been shipped.",
  "type": "success",
  "data": { "orderId": "12345", "trackingUrl": "https://..." }
}

Broadcast to company:

POST /notification/admin/broadcast
{
  "companyId": "uuid",
  "title": "System Maintenance",
  "message": "The system will be down for maintenance at 2:00 AM.",
  "type": "warning"
}

Entities

Core Entities

| Entity | Table | Description | |--------|-------|-------------| | Notification | notification | User notification with type, title, message, JSON data, isRead, readAt |

Indexes: (userId, isRead), (userId, createdAt) for fast unread queries.

Company Feature Entities (enableCompanyFeature: true)

| Entity | Table | Description | |--------|-------|-------------| | NotificationWithCompany | notification | Same + companyId column |

Additional index: (companyId, userId, isRead)

import { NotificationModule } from '@flusys/nestjs-notification';

TypeOrmModule.forRoot({
  entities: [
    ...NotificationModule.getEntities({ enableCompanyFeature: true }),
  ],
})

Cross-Module Adapter (NOTIFICATION_ADAPTER)

The NOTIFICATION_ADAPTER token is defined in @flusys/nestjs-shared so any module can send notifications without importing @flusys/nestjs-notification.

When NotificationModule is registered in the app, it provides the implementation. When it is not registered, @Optional() prevents injection errors.

INotificationAdapter interface (from @flusys/nestjs-shared):

interface INotificationAdapter {
  send(data: INotificationSendData): Promise<void>;
  sendBulk(data: INotificationSendData[]): Promise<void>;
  broadcast(companyId: string, data: Omit<INotificationSendData, 'userId'>): Promise<void>;
  isUserOnline(userId: string): boolean;
}

interface INotificationSendData {
  userId: string;
  title: string;
  message: string;
  type?: 'info' | 'success' | 'warning' | 'error';
  data?: Record<string, any>;
  companyId?: string;
}

Usage pattern:

import { NOTIFICATION_ADAPTER, INotificationAdapter } from '@flusys/nestjs-shared/interfaces';

@Injectable()
export class OrderService {
  constructor(
    @Optional() @Inject(NOTIFICATION_ADAPTER)
    private readonly notifications?: INotificationAdapter,
  ) {}

  async shipOrder(orderId: string, userId: string): Promise<void> {
    // ... ship order logic

    // Send notification if adapter is available
    await this.notifications?.send({
      userId,
      title: 'Order Shipped',
      message: `Your order has been shipped.`,
      type: 'success',
      data: { orderId },
    });
  }
}

Exported Services

| Service | Description | |---------|-------------| | NotificationService | Notification CRUD (REQUEST-scoped) | | NotificationHelperService | Singleton service for programmatic sending | | NotificationGateway | Socket.IO gateway (when enableRealtime: true) | | NotificationConfigService | Runtime config and feature flags | | NotificationDataSourceProvider | Dynamic DataSource per request | | NOTIFICATION_ADAPTER | Cross-module adapter implementation |


Sending Notifications Programmatically

Use NotificationHelperService (singleton) within the same application:

import { NotificationHelperService } from '@flusys/nestjs-notification';

@Injectable()
export class PaymentService {
  constructor(
    @Inject(NotificationHelperService)
    private readonly notificationHelper: NotificationHelperService,
  ) {}

  async afterPayment(userId: string, amount: number): Promise<void> {
    await this.notificationHelper.send({
      userId,
      title: 'Payment Received',
      message: `We received your payment of $${amount}.`,
      type: 'success',
      data: { amount },
    });
  }

  async notifyCompany(companyId: string): Promise<void> {
    await this.notificationHelper.broadcast(companyId, {
      title: 'Company Announcement',
      message: 'Check your email for important updates.',
      type: 'info',
    });
  }
}

Frontend Integration

Unread Badge Count

// Poll for unread count (or update via WebSocket event 'notification:count')
async getUnreadCount(): Promise<number> {
  const response = await this.http.post('/notification/get-unread-count', {}).toPromise();
  return response.data.count;
}

Notification List

async getNotifications(page = 1, pageSize = 20) {
  return this.http.post('/notification/get-all', { page, pageSize }).toPromise();
}

Mark as Read

// Single
await this.http.post('/notification/mark-read', { id: notificationId }).toPromise();

// All
await this.http.post('/notification/mark-all-read', {}).toPromise();

Troubleshooting

WebSocket connection rejected (401)

The JWT token in the auth.token field is invalid or expired. Ensure you pass Bearer <token> format:

auth: { token: `Bearer ${accessToken}` }

Notifications delivered via REST but not real-time

Check that enableRealtime: true is set in the config and @nestjs/platform-socket.io is installed. Also verify that the client is connecting to the correct namespace (/notifications).


Company broadcast not reaching all users

Users must join the company room via the join:company WebSocket event after connecting. If enableCompanyFeature: false, company rooms are not created.


NOTIFICATION_ADAPTER is undefined

NotificationModule is not registered. Use @Optional() @Inject(NOTIFICATION_ADAPTER) so the injection doesn't throw when the module is absent.


No metadata for entity

Register entities in TypeOrmModule:

entities: [...NotificationModule.getEntities({ enableCompanyFeature: true })]

License

MIT © FLUSYS


Part of the FLUSYS framework — a full-stack monorepo powering Angular 21 + NestJS 11 applications.