@nolag/feed
Activity feeds with posts, likes, comments, and fan-out delivery.
Overview
@nolag/feed powers real-time activity feeds for social apps, community platforms, and content aggregators. Posts, likes, and comments are delivered live to every subscriber of a channel. Fan-out happens on the broker: publish once to a channel and every subscriber receives the update. Your app owns one core NoLag client and injects it into NoLagFeed; the wrapper attaches its behaviour to that connection.
Key Features
- Real-time post, like, and comment delivery to all channel subscribers
- Unread post tracking with per-channel badge counts
- Unlike support with live tally updates
- Flat comments per post, with running comment counts
- Audience filters that likes and comments inherit from their post
- Automatic reconnection with subscriptions and presence restored
How It Works
NoLagFeed attaches to an injected @nolag/js-sdk client. Calling joinChannel() creates a FeedChannel that subscribes to three topics: posts for post creation events, reactions for like and unlike events, and comments for comment creation events. A PostStore accumulates posts and their reaction counts, while comments are grouped by post ID. An unread counter tracks posts that arrived while the channel was not active. The app owns the socket lifecycle; the wrapper never opens or closes it.
| Topic | Purpose |
|---|---|
posts | Post creation events: content, media, author, timestamp |
reactions | Like and unlike events with running tally |
comments | Comment creation events keyed by post ID |
A subscriber sees posts, likes, and comments published from the moment it joins. A fresh subscribe or an ordinary reconnect never replays history; see Replay for the one case where the broker replays messages. Keep your timeline of record in your own database.
Before you start. Create an app from the nolag-feed-sdk blueprint. That seeds the room this SDK expects (main-feed) and the online lobby, and returns an app slug with a random suffix. That slug is the appName you pass to the wrapper. You can also do this in the portal: Apps, New App, pick the blueprint.
import { NoLagApi } from "@nolag/js-sdk";
const api = new NoLagApi(process.env.NOLAG_API_KEY); // nlg_live_...
const app = await api.apps.create({ name: "My App", blueprintId: "nolag-feed-sdk" });
console.log(app.slug); // e.g. "my-app-a3f9": this is your appName
const actor = await api.actors.create({ name: "web-client", actorType: "user" });
console.log(actor.accessToken); // shown once; keep itInstallation
npm install @nolag/feed @nolag/js-sdkShared connection. One core NoLag client can back several wrapper SDKs at once, for example a feed, chat, and notify on a single socket, as long as each wrapper uses a distinct appName. Each wrapper attaches its handlers on construction and releases them with detach(), and never touches the socket itself. Your app owns connect() and disconnect().
Quick Start
import { NoLag } from '@nolag/js-sdk'
import { NoLagFeed } from '@nolag/feed'
// The app owns one core client. In a browser, pass a token provider so the
// SDK can mint fresh short-lived client tokens from your backend.
const client = NoLag(async () => (await (await fetch('/api/nolag-token')).json()).token)
// Inject the client into the feed wrapper
const feed = new NoLagFeed({
client,
appName: APP_SLUG, // the slug returned when you created the app
username: 'Alice',
avatar: '/img/alice.png',
})
await client.connect() // the app owns the connection
await feed.ready() // wrapper setup complete
// Join the seeded channel
const channel = feed.joinChannel('main-feed')
// Listen for new posts from other users in real time
channel.on('postCreated', (post) => {
console.log(`${post.username}: ${post.content}`)
console.log('Likes:', post.likeCount)
})
// Create a post. Publishers never receive their own posts back, so the
// wrapper adds it to the local store and emits `postSent`.
const mine = channel.createPost({
content: 'Just shipped a new feature!',
media: [{ type: 'image', url: '/uploads/screenshot.png' }],
})
// Like a post
channel.likePost(mine.id)
channel.on('postLiked', ({ postId, userId, likeCount }) => {
console.log(`${userId} liked ${postId}; now ${likeCount} likes`)
})
// Comment on a post
channel.addComment(mine.id, 'Congrats!')
channel.on('commentAdded', (comment) => {
console.log(`New comment on ${comment.postId}: ${comment.text}`)
})
// Get unread count and mark as read
console.log('Unread:', channel.unreadCount)
channel.markRead()
// Teardown: the wrapper releases its handlers; the app closes the socket.
feed.detach()
client.disconnect()API Reference
NoLagFeed
The main class. Attaches to the injected core client, manages global user presence, and the feed channel lifecycle.
Constructor Options
| Option | Type | Description |
|---|---|---|
client | NoLagSocket | Required. The injected core NoLag client the app owns and connects. |
username | string | Required. Display name for this user. |
avatar | string | Optional avatar URL. |
metadata | Record<string, unknown> | Optional custom user data attached to presence. |
appName | string | The app slug returned when you created the app (default 'feed', which only works if an app with exactly that slug exists). |
channels | string[] | Channels to subscribe to once the wrapper is ready. |
maxPostCache | number | Max posts kept in memory per channel (default 200). |
maxCommentCache | number | Accepted and stored (default 100) but not enforced yet: comments per post are kept without a cap. |
debug | boolean | Enable wrapper debug logging (default false). |
| Method | Returns | Description |
|---|---|---|
ready() | Promise<void> | Resolves once wrapper setup completed |
detach() | void | Release this wrapper's handlers and topics; terminal, never closes the socket |
joinChannel(name, opts?) | FeedChannel | Join a feed channel; opts.filters limits delivery to matching posts (and their likes and comments); throws before ready() |
leaveChannel(name) | void | Leave a channel and unsubscribe from its topics |
getChannels() | FeedChannel[] | All joined channels |
getOnlineUsers() | FeedUser[] | All users currently online in the lobby |
updateProfile(updates) | void | Update display name, avatar, or metadata broadcast to peers |
Events: NoLagFeed
| Event | Payload | Description |
|---|---|---|
connected | none | Wrapper setup completed on a fresh connection |
disconnected | reason: string | Connection closed |
reconnecting | none | The client is attempting to reconnect |
reconnected | none | Reconnection successful; channels are restored automatically |
error | error: Error | Unrecoverable error occurred |
userOnline | user: FeedUser | A user has come online |
userOffline | user: FeedUser | A user has gone offline |
FeedChannel
Returned by joinChannel(). Handles posts, reactions, comments, and unread tracking for a single channel.
| Method / Property | Returns | Description |
|---|---|---|
createPost(opts) | FeedPost | Publish a new post from { content, media?, data?, filter?, filters? }; returns the optimistic local copy |
likePost(postId) | void | Like a post; increments the like count for all subscribers |
unlikePost(postId) | void | Remove your like from a post |
addComment(postId, text) | FeedComment | Add a comment to the specified post; returns the local copy |
getPosts() | FeedPost[] | All posts in the local store, oldest first |
getComments(postId) | FeedComment[] | All comments for the specified post |
getUsers() | FeedUser[] | Remote users present in this channel |
setFilters(values, opts?) | void | Replace the channel's subscription filters; see Filters |
unreadCount | number | Posts that arrived since markRead() was last called |
markRead() | void | Reset the unread count to zero |
A FeedPost has id, userId, username, avatar?, content, media?, data?, likeCount, commentCount, likedByMe, timestamp, filter?, status, and isReplay. A FeedComment has id, postId, userId, username, avatar?, text, timestamp, and isReplay. Media attachments are { type: 'image' | 'video' | 'link', url, thumbnail?, title? }.
Events: FeedChannel
| Event | Payload | Description |
|---|---|---|
postCreated | FeedPost | A new post arrived from another user |
postSent | FeedPost | Your own post was added to the local store and published |
postLiked | { postId: string, userId: string, likeCount: number } | A post received a new like (yours included) |
postUnliked | { postId: string, userId: string, likeCount: number } | A like was removed from a post |
commentAdded | FeedComment | A comment arrived from another user |
commentSent | FeedComment | Your own comment was added to the local store and published |
subscriberJoined | user: FeedUser | A user joined this channel |
subscriberLeft | user: FeedUser | A user left this channel |
unreadChanged | { channel: string, count: number } | The unread post count for this channel changed |