@nestbolt/notifications
Database Channel
Store notifications in the database with full read/unread tracking, querying by entity, and bulk operations.
The database channel persists notifications to a notifications table using TypeORM. It is the default channel and is enabled automatically unless explicitly disabled.
How It Works
When a notification is sent via the database channel, the DatabaseChannel does the following:
- Calls
notification.toDatabase()to get the data payload. - Creates a
NotificationEntitywith the notification type, notifiable entity metadata, channel name, and data. - Saves the entity to the database.
// This is what happens internally
const entity = repo.create({
type: notification.notificationType(), // e.g., "WelcomeNotification"
notifiableType: notifiable.getNotifiableType(), // e.g., "User"
notifiableId: notifiable.getNotifiableId(), // e.g., "abc-123"
channel: "database",
data: notification.toDatabase(), // e.g., { message: "Welcome!" }
});
await repo.save(entity);NotificationEntity Schema
The NotificationEntity is a TypeORM entity mapped to the notifications table:
| Column | Type | Nullable | Description |
|---|---|---|---|
id | uuid | No | Auto-generated UUID primary key. |
type | varchar(255) | No | The notification type from notificationType(). |
notifiable_type | varchar(255) | No | The entity type (e.g., "User"). |
notifiable_id | varchar(255) | No | The entity ID. |
channel | varchar(255) | No | Always "database" for this channel. |
data | simple-json | No | JSON-serialized notification payload. |
read_at | datetime | Yes | Timestamp when marked as read. null means unread. |
created_at | datetime | No | Auto-set on creation. |
updated_at | datetime | No | Auto-updated on save. |
Indexes
The entity defines two composite indexes for efficient querying:
(notifiable_type, notifiable_id)-- for fetching all notifications for a specific entity.(notifiable_type, notifiable_id, read_at)-- for fetching unread notifications and counts.
Computed Properties
The NotificationEntity includes two getter properties:
// Returns true if the notification has been read
notification.isRead; // boolean
// Returns true if the notification has not been read
notification.isUnread; // booleanInstance Methods
// Mark as read (sets readAt to current date)
notification.markAsRead();
// Mark as unread (sets readAt to null)
notification.markAsUnread();Note that these instance methods only update the in-memory entity. You must save the entity to persist the change. The NotificationService methods handle saving automatically.
Querying Notifications
Via NotificationService
The NotificationService provides methods for querying notifications by notifiable type and ID:
getNotifications(notifiableType, notifiableId)
Returns all notifications for a given entity, ordered by creation date (newest first):
const notifications = await notificationService.getNotifications("User", userId);
for (const notification of notifications) {
console.log(notification.type); // "WelcomeNotification"
console.log(notification.data); // { message: "Welcome!" }
console.log(notification.isRead); // false
console.log(notification.createdAt); // Date
}getUnreadNotifications(notifiableType, notifiableId)
Returns only unread notifications (where read_at is null), ordered by creation date (newest first):
const unread = await notificationService.getUnreadNotifications("User", userId);getUnreadCount(notifiableType, notifiableId)
Returns the number of unread notifications:
const count = await notificationService.getUnreadCount("User", userId);
// count: 5Via the Entity Mixin
If your entity uses NotifiableMixin, you can query notifications directly on the entity instance:
// All notifications
const all = await user.getNotifications();
// Unread only
const unread = await user.unreadNotifications();
// Unread count
const count = await user.getUnreadNotificationCount();These mixin methods call the corresponding NotificationService methods internally, using the entity's getNotifiableType() and getNotifiableId() values.
Read/Unread Tracking
markAsRead(notificationId)
Marks a single notification as read by setting its read_at timestamp. Emits a notification.read event.
await notificationService.markAsRead(notificationId);If the notification ID does not exist, the method returns without error.
markAsUnread(notificationId)
Marks a notification as unread by setting read_at back to null:
await notificationService.markAsUnread(notificationId);markAllAsRead(notifiableType, notifiableId)
Marks all unread notifications for a given entity as read in a single database update. Emits a notification.all-read event if at least one notification was updated:
await notificationService.markAllAsRead("User", userId);deleteNotification(notificationId)
Permanently removes a notification from the database:
await notificationService.deleteNotification(notificationId);Via the Entity Mixin
// Mark a specific notification as read
await user.markNotificationAsRead(notificationId);
// Mark a specific notification as unread
await user.markNotificationAsUnread(notificationId);
// Mark all as read
await user.markAllNotificationsAsRead();Using Notifications in an API
Here is a complete example of a notifications controller exposing a REST API:
import { Controller, Get, Param, Patch, Delete, Req } from "@nestjs/common";
import { NotificationService } from "@nestbolt/notifications";
@Controller("notifications")
export class NotificationsController {
constructor(private readonly notificationService: NotificationService) {}
@Get()
async list(@Req() req: any) {
return this.notificationService.getNotifications(
"User",
req.user.id,
);
}
@Get("unread")
async unread(@Req() req: any) {
return this.notificationService.getUnreadNotifications(
"User",
req.user.id,
);
}
@Get("unread/count")
async unreadCount(@Req() req: any) {
const count = await this.notificationService.getUnreadCount(
"User",
req.user.id,
);
return { count };
}
@Patch(":id/read")
async markRead(@Param("id") id: string) {
await this.notificationService.markAsRead(id);
return { success: true };
}
@Patch(":id/unread")
async markUnread(@Param("id") id: string) {
await this.notificationService.markAsUnread(id);
return { success: true };
}
@Patch("read-all")
async markAllRead(@Req() req: any) {
await this.notificationService.markAllAsRead("User", req.user.id);
return { success: true };
}
@Delete(":id")
async remove(@Param("id") id: string) {
await this.notificationService.deleteNotification(id);
return { success: true };
}
}Filtering Notifications by Type
The NotificationService query methods return all notification types. To filter by a specific type, use standard array methods on the result:
const notifications = await notificationService.getNotifications("User", userId);
const orderNotifications = notifications.filter(
(n) => n.type === "OrderShippedNotification",
);
const systemNotifications = notifications.filter(
(n) => n.type.startsWith("System"),
);For more advanced filtering at the database level, you can inject the TypeORM repository directly:
import { InjectRepository } from "@nestjs/typeorm";
import { Repository } from "typeorm";
import { NotificationEntity } from "@nestbolt/notifications";
@Injectable()
export class CustomNotificationService {
constructor(
@InjectRepository(NotificationEntity)
private readonly repo: Repository<NotificationEntity>,
) {}
async getOrderNotifications(userId: string): Promise<NotificationEntity[]> {
return this.repo.find({
where: {
notifiableType: "User",
notifiableId: userId,
type: "OrderShippedNotification",
},
order: { createdAt: "DESC" },
});
}
}Disabling the Database Channel
If you do not need database notifications, explicitly disable the channel:
NotificationModule.forRoot({
channels: {
database: false,
mail: { /* ... */ },
},
});When disabled, the DatabaseChannel provider is not registered, and using "database" in a notification's via() method will throw a ChannelNotFoundException.
Mail Channel
Send email notifications using the MailMessage fluent builder API -- subjects, greetings, content lines, call-to-action buttons, attachments, and recipient control.
Custom Channels
Implement custom notification channels for Slack, SMS, webhooks, or any delivery transport by implementing the NotificationChannel interface.