Headless SDK & TanStack Query
A framework-neutral client for the inbox API + SSE — fetch functions, a live subscribe(), and TanStack Query option factories that work in React and Vue. For frontends separate from the backend.
The <Inbox/> widget is React-specific. When your frontend lives in a
separate repo, points at a different backend, or isn't React, reach for
@dudousxd/nestjs-notifications-client — a headless, framework-neutral SDK: typed fetch methods, a
live subscribe() over SSE, and TanStack Query option factories.
pnpm add @dudousxd/nestjs-notifications-clientOn NestJS and want the client generated (and kept in lockstep with your routes)? Use codegen instead. This package is the hand-written, zero-config alternative.
The client
import { createNotificationsClient } from '@dudousxd/nestjs-notifications-client';
const client = createNotificationsClient({
baseUrl: 'https://api.example.com/', // read API mounted at /notifications
sseUrl: 'https://api.example.com/notifications/stream', // optional live stream
headers: { Authorization: `Bearer ${token}` }, // or credentials: 'include'
});
await client.list({ page: 1 });
await client.unread();
await client.unreadCount();
await client.markAsRead(id);
await client.markAllAsRead();
await client.remove(id);It uses the global fetch; pass a fetch impl for non-browser runtimes.
Live updates with subscribe()
const stop = client.subscribe(({ count }) => {
// `count` is set when the server pushes it in the SSE payload; otherwise re-fetch.
if (count != null) setBadge(count);
else client.unreadCount().then(setBadge);
});
// later: stop()subscribe() is SSR-safe — when there's no EventSource (or no sseUrl), it returns a no-op
unsubscribe, so it's safe to call during server rendering.
TanStack Query
The /tanstack subpath exports framework-neutral option factories — they return plain
queryOptions/mutationOptions-shaped objects, so they drop into @tanstack/react-query and
@tanstack/vue-query alike.
import { createNotificationsClient } from '@dudousxd/nestjs-notifications-client';
import {
notificationKeys,
notificationQueries,
notificationMutations,
} from '@dudousxd/nestjs-notifications-client/tanstack';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
const client = createNotificationsClient({ baseUrl: '/' });
function Bell() {
const { data: count } = useQuery(notificationQueries.unreadCount(client));
return <span>🔔 {count}</span>;
}
function useMarkRead() {
const qc = useQueryClient();
return useMutation({
...notificationMutations.markAsRead(client),
onSuccess: () => qc.invalidateQueries({ queryKey: notificationKeys.all }),
});
}import { useQuery } from '@tanstack/vue-query';
import { notificationQueries } from '@dudousxd/nestjs-notifications-client/tanstack';
const { data } = useQuery(notificationQueries.list(client, { page: 1 }));notificationKeys gives you the stable query keys for cache reads and invalidation
(notificationKeys.all, .list(params), .unread(), .unreadCount()).
Which client should I use?
React inbox widget
Drop a notification inbox into a React app with one component. The @dudousxd/nestjs-notifications-react package ships <Inbox/>, a NotificationsProvider, and the useNotifications / useUnreadCount hooks — all consuming the read API and SSE stream you already expose.
Live unread badge
A notification bell whose unread count updates the instant a notification arrives — SSE pushes the change, no polling. Combine the SSE channel with the unread count from the database channel.