---
title: "Feed SDK"
description: "Activity feeds with posts, likes, comments, and fan-out delivery."
---

# @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 all subscribers of a channel and replayed for up to 30 days so users joining late always see a full timeline. Fan-out happens server-side: publish once to a channel and every subscriber receives the update instantly. 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
- 30-day replay across all three topics: posts, reactions, and comments
- Unread post tracking with per-channel badge counts
- Unlike support with live tally updates
- Nested comment threads per post
- Automatic reconnection with channel and state restoration

## 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 a `CommentStore` groups comments by post ID. An unread counter tracks posts that arrived while the user was away. The app owns the socket lifecycle; the wrapper never opens or closes it.

| Topic | Purpose | Replay |
| --- | --- | --- |
| `posts` | Post creation events: text, media, author, timestamp | 30 days |
| `reactions` | Like and unlike events with running tally | 30 days |
| `comments` | Comment creation events keyed by post ID | 30 days |

## Installation

```bash [Terminal]
npm install @nolag/feed @nolag/js-sdk
```

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

```typescript [TypeScript]
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, username: 'Alice', avatar: '/img/alice.png' })

await client.connect()   // the app owns the connection
await feed.ready()       // wrapper setup complete

// Join a feed channel (e.g. a user timeline or community feed)
const channel = await feed.joinChannel('user-123-timeline')

// Listen for new posts in real time
channel.on('postCreated', (post) => {
  console.log(`${post.author.name}: ${post.text}`)
  console.log('Likes:', post.likeCount)
})

// Create a post
await channel.createPost({
  text: 'Just shipped a new feature!',
  media: [{ type: 'image', url: '/uploads/screenshot.png' }],
})

// Like a post
await channel.likePost('post-abc')

channel.on('postLiked', ({ postId, likeCount }) => {
  console.log(`Post ${postId} now has ${likeCount} likes`)
})

// Comment on a post
await channel.addComment('post-abc', 'Congrats!')

channel.on('commentAdded', ({ postId, comment }) => {
  console.log(`New comment on ${postId}: ${comment.text}`)
})

// Get unread count and mark as read
console.log('Unread:', channel.unreadCount)
await 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` | NoLag app for topic prefixes (default `'feed'`). |
| `channels` | `string[]` | Channels to subscribe to once the wrapper is ready. |
| `maxPostCache` | `number` | Max posts kept in memory per channel (default `200`). |
| `maxCommentCache` | `number` | Max comments kept in memory per post (default `100`). |
| `debug` | `boolean` | Enable wrapper debug logging (default `false`). |

| Method | Description |
| --- | --- |
| `ready()` | Resolves once wrapper setup completed |
| `detach()` | Release this wrapper's handlers and topics; terminal, never closes the socket |
| `joinChannel(name)` | Join a feed channel; returns a `FeedChannel` instance |
| `leaveChannel(name)` | Leave a channel and unsubscribe from its topics |
| `getOnlineUsers()` | Return all users currently online across all joined channels |

### Events: NoLagFeed

| Event | Payload | Description |
| --- | --- | --- |
| `connected` | none | WebSocket connection established |
| `disconnected` | `reason: string` | Connection closed |
| `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 | Description |
| --- | --- |
| `createPost(opts)` | Publish a new post with text, optional media array, and custom metadata |
| `likePost(postId)` | Like a post; increments the like count for all subscribers |
| `unlikePost(postId)` | Remove your like from a post |
| `addComment(postId, text)` | Add a comment to the specified post |
| `getPosts()` | Return all posts in the local store, newest first |
| `getComments(postId)` | Return all comments for the specified post |
| `unreadCount` | Number of posts that arrived since `markRead()` was last called |
| `markRead()` | Reset the unread count to zero |

### Events: FeedChannel

| Event | Payload | Description |
| --- | --- | --- |
| `postCreated` | `FeedPost` | A new post arrived from another user |
| `postSent` | `FeedPost` | Confirmation that your own post was delivered |
| `postLiked` | `{ postId: string, likeCount: number, likedBy: string }` | A post received a new like |
| `postUnliked` | `{ postId: string, likeCount: number, unlikedBy: string }` | A like was removed from a post |
| `commentAdded` | `{ postId: string, comment: FeedComment }` | A comment was added to a post |
| `commentSent` | `{ postId: string, comment: FeedComment }` | Confirmation that your own comment was delivered |
| `subscriberJoined` | `user: FeedUser` | A user joined this channel |
| `subscriberLeft` | `user: FeedUser` | A user left this channel |
| `replayStart` | `{ count: number }` | Historical feed replay is beginning |
| `replayEnd` | `{ replayed: number }` | Historical feed replay is complete |
| `unreadChanged` | `{ count: number }` | The unread post count for this channel changed |
