Comments & Discussions
Rules
- Threaded comments:
parent_id foreign key on comments table — null for top-level, references parent for replies
- Optimistic UI: add comment to UI immediately, confirm with server response, rollback on error
- @mentions: parse
@username in comment body — trigger user lookup dropdown on @ keystroke, notify mentioned users
- Reactions: separate
reactions table (commentId, userId, emoji) — unique constraint on (commentId, userId, emoji)
- Moderation:
status field (published, flagged, removed) — moderation queue for flagged content, auto-flag with word list
- Soft delete: set
deletedAt timestamp — show "deleted" placeholder for comments with replies, fully hide orphaned deleted comments
- Cursor pagination:
cursor (commentId) + limit — ordered by createdAt, load older comments on scroll
- Edit history: store edits in separate table or JSON array — show "edited" indicator with last edit timestamp
Patterns
const comments = await db.query.comments.findMany({
where: and(eq(comments.postId, postId), isNull(comments.parentId)),
with: {
author: { columns: { id: true, name: true, avatar: true } },
replies: {
with: { author: { columns: { id: true, name: true, avatar: true } } },
orderBy: [asc(comments.createdAt)],
},
reactions: true,
},
orderBy: [desc(comments.createdAt)],
limit: 20,
});
const addComment = useMutation({
mutationFn: (body: string) => api.post(`/posts/${postId}/comments`, { body }),
onMutate: async (body) => {
await queryClient.cancelQueries({ queryKey: ["comments", postId] });
const previous = queryClient.getQueryData(["comments", postId]);
queryClient.setQueryData(["comments", postId], (old: Comment[]) => [
{ id: `temp-${Date.now()}`, body, author: currentUser, createdAt: new Date() },
...old,
]);
return { previous };
},
onError: (_err, _body, context) => {
queryClient.setQueryData(["comments", postId], context?.previous);
},
onSettled: () => queryClient.invalidateQueries({ queryKey: ["comments", postId] }),
});
Avoid
- Unlimited nesting depth — cap at 2-3 levels, then flatten deeper replies
- Loading all comments at once — use cursor pagination, especially for active discussions
- Client-side mention parsing only — validate and extract mentions server-side for notifications
- Hard deleting comments with replies — this orphans the thread; soft delete and show placeholder
- Reactions without unique constraints — allows duplicate reactions from the same user