Chat Kit

Full-featured iMessage/WhatsApp-inspired chat with 1-1 and group conversations, attachments, polls, GIFs, location sharing, reactions, and Socket.IO real-time transport.

Quick reference

CLI commands

Bash
# Add Chat UI components npx @fivfold/ui add chat # Add Chat backend (NestJS + TypeORM + Socket.IO) npx @fivfold/api add chat --framework=nestjs --orm=typeorm # Use MongoDB + Mongoose npx @fivfold/api add chat --framework=nestjs --orm=mongoose # Use Express + Prisma npx @fivfold/api add chat --framework=express --orm=prisma # Dry run to preview generated files npx @fivfold/ui add chat --dry-run npx @fivfold/api add chat --dry-run

Overview

The Chat Kit is a production-ready messaging system scaffolded across your full stack. It follows Hexagonal Architecture so you can swap databases, ORMs, and real-time providers without touching your domain logic. Supported combinations:

LayerOptions
FrameworkNestJS, Express
SQLTypeORM (PostgreSQL, MySQL, MariaDB, MSSQL), Prisma
NoSQLMongoose (MongoDB), Prisma (MongoDB connector)
Cloud NoSQLAzure Cosmos DB SDK, AWS DynamoDB SDK
RealtimeSocket.IO (WebSocket — auto-selected)

1-1 & Group Chats

Private DMs and multi-participant group conversations with admin roles, member management, and leave/remove actions.

Rich Messages

Text, images, videos, files, audio, GIFs (Tenor API), location pins, and interactive polls — all sent as typed message variants.

Real-time Updates

Socket.IO WebSocket rooms with typing indicators, presence detection, read receipts, and message delivery events — works with any database.

Reactions & Read Receipts

Per-message emoji reactions, per-message read-by tracking, and animated typing indicators with user avatars.

Search

Full-text search across conversations and message content with paginated results grouped by type.

Glassmorphism UI

Backdrop-blur message bubbles, smooth framer-motion animations, and fully responsive layout from mobile to desktop.

Architecture

The Chat Kit backend strictly follows Hexagonal (Ports & Adapters) Architecture. The domain port (IChatService) is framework and database agnostic — the delivery and infrastructure layers are generated separately based on your stack selection.

Chat Architecture

Demo

Interactive preview with simulated real-time replies and mock data

Messages
SJ
Sarah Johnson2m
Sounds great! See you at 3pm 🎉
DT
Design Team1h
Alex: New mockups uploaded to Figma
MC
Marcus Chen3h
Did you see the new product launch?
EW
Emma WilsonYesterday
The report is ready for review
SJ
SJ
Hey! Are you free for a quick call today?
10:30 AM
Sure, what time works for you?
10:32 AM
SJ
How about 3pm?
10:33 AM
SJ
Sounds great! See you at 3pm 🎉
10:34 AM
Contact Info
SJ

Sarah Johnson

Online

Shared Media

Guide

Step-by-step guides for the frontend UI and backend API integration. In the sidebar, pick Frontend first (Next.js or Vite), then runtime through ORM; the API tab uses the same selection for CORS and connection steps.

The Chat Kit UI is built on shadcn/ui with Tailwind CSS v4. All components are fully customizable and support light/dark mode via CSS variables.

1Install the Chat Kit

Run the FivFold UI CLI after initializing your project with npx @fivfold/ui init.

Bash
npx @fivfold/ui add chat

Components are placed in @/components/ui/kits/chat/.

2Generated file structure

File tree
kits/chat/ types.ts # All shared TypeScript types index.tsx # ChatKit root component + re-exports chat-avatar.tsx # ChatAvatar, GroupAvatar threads-list.tsx # Sidebar conversation list thread-item.tsx # Individual thread row with context menu conversation.tsx # Message view with infinite scroll message-bubble.tsx # Message bubble (text, image, poll, location, GIF) message-input.tsx # Compose bar with attachment/GIF/poll/location typing-indicator.tsx # Animated typing dots attachment-picker.tsx # File/image/video picker with drag-and-drop gif-picker.tsx # Tenor GIF search and picker poll-creator.tsx # Create poll dialog poll-display.tsx # Rendered poll with vote progress location-picker.tsx # Share location (geolocation or manual) location-display.tsx # Static map + Google Maps link reaction-picker.tsx # Quick emoji reaction overlay search-panel.tsx # Full-text search dialog new-chat-dialog.tsx # Start DM or create group dialog contact-detail.tsx # Contact info sheet group-detail.tsx # Group info, members, admin controls

3Import and use in your app

app/chat/page.tsx
import { ChatKit } from "@/components/ui/kits/chat"; import type { FivFoldChatConversation, FivFoldChatMessage, FivFoldChatUser, } from "@/components/ui/kits/chat"; export default function ChatPage() { const currentUser: FivFoldChatUser = { id: "u1", name: "You", email: "you@example.com", }; return ( <ChatKit currentUser={currentUser} conversations={conversations} onSendMessage={handleSendMessage} onCreateConversation={handleCreate} onVotePoll={handleVote} onAddReaction={handleReaction} onMarkRead={handleMarkRead} /> ); }

4Props reference (ChatKit)

PropTypePurpose
currentUserFivFoldChatUserThe logged-in user. Used to determine sent/received and show own avatar.
conversationsFivFoldChatConversation[]All conversations to display in the sidebar thread list.
onSendMessage(convId, dto) => voidCalled when user sends any message type (text, attachment, poll, location, GIF).
onCreateConversation(dto) => Promise<Conversation>Called when user starts a new DM or group. Return the created conversation.
onVotePoll(pollId, optionIds) => voidCalled when user votes on a poll. PATCH /api/chat/polls/:id/vote.
onAddReaction(msgId, emoji) => voidCalled when user reacts to a message. POST /api/chat/messages/:id/reactions.
onMarkRead(msgId) => voidCalled when a message enters the viewport. PATCH /api/chat/messages/:id/read.
typingUsersRecord<string, string[]>Map of convId → userIds currently typing. Drives the animated typing indicator.
onTyping(convId, isTyping) => voidEmit typing start/stop events to your real-time backend.
loadingMessagesbooleanShow skeleton loaders while fetching message history.
hasMoreMessagesbooleanEnable the "load more" trigger at the top of the message list.
onLoadMore() => voidCalled when user scrolls to the top. Fetch the next page of messages.

5Integration with backend

Connecting Chat (UI layer) data to your user system

The kit needs a currentUser object and conversation data from your API. User ids in props, REST calls, and Socket.IO handshakes must all refer to the same identifiers your backend stores on participants and messages.

  • Set currentUser.id to the same value the API uses for req.user.id (or your dev-user header during local testing).
  • When rendering participants and avatars, map userId fields from DTOs to display names via your user directory.
  • Socket.IO query userId and REST headers must stay in sync so typing and read receipts target the right participant.

Inspect generated code

Open the scaffolded entities, schemas, or Prisma models for this kit and compare field names (userId, ownerId, etc.) to your User table or IdP subject. Migrate or add FKs where needed—FivFold cannot know your prior schema.

You own the wiring

The step-by-step guides, environment variables, and dev-server proxy or rewrite examples are suggestions—not a mandated layout. You should read the generated files, your existing routes and auth, and your deployment topology, then choose URLs, origins, and headers that match your app. We encourage verifying every connection yourself before shipping.

After you add Chat UI + Chat API — checklist

Scaffolding aligns routes and dependencies; your app still needs a thin integration layer. Work through these in order for local dev:

  1. Identity on the API: Nest registers ChatModule but does not auto-wire ChatDevUserMiddleware in main.ts. Either register it (see below) or use real auth so req.user.id exists — otherwise REST handlers throw at runtime.
  2. Socket.IO through the dev server: Add a rewrite for /socket.io to your API (same idea as REST), or point socket.io-client at the API origin.
  3. Same user id everywhere: send X-User-Id on fetch (or JWT) matching currentUser.id in the kit and the server's dev default / env (DEV_USER_ID).
  4. Integration host: ChatKit is presentational — add a page or module that loads conversations/messages from your API, maps DTOs to FivFoldChat* types, and implements onSendMessage, search, and socket listeners (see examples below).
  5. Generic FE ↔ BE patterns (proxy, CORS) also apply — see Installation → Connecting UI and API.

Full-stack local dev (your app code)

FivFold adds the ChatKit components and installs dependencies. Everything below is outside the kit: dev-server routing to your API, env vars, fetch/socket helpers, and a thin host that maps API DTOs to FivFoldChat* types. Match the Frontend choice in the stack sidebar (Next.js).

Not for production as-is

The patterns below use a dev user header and permissive CORS. Replace with JWT (or your Auth kit) before shipping.

Step-by-step: Chat UI → API

Examples match the Frontend choice in the stack sidebar (Next.js). Your mount paths may differ; adjust prefixes to match how you register routes and proxies.

Next.js → API

Prefer rewrites in next.config.ts so browser code can use same-origin paths like /api/chat/... while the dev server forwards to your backend. Alternatively set NEXT_PUBLIC_API_URL and call that base from client code.

next.config.ts
// next.config.ts — example rewrite to API on another port import type { NextConfig } from 'next'; const nextConfig: NextConfig = { async rewrites() { return [ { source: '/api/:path*', destination: 'http://localhost:3001/:path*', // your Nest/Express port }, ]; }, }; export default nextConfig;

If the app and API both use port 3000, run the API on a different port (e.g. 3001) or use a monorepo BFF pattern.

API → browser (CORS)

Allow your UI's dev origin on the API. For Next.js that is typically http://localhost:3000 (plus 127.0.0.1 if you use it).

Production

Use your real API base (e.g. https://api.example.com/chat) and lock CORS to known web origins — not *.

Realtime: For Socket.IO with Next.js dev, rewrite `/socket.io` to your API host (same pattern as REST) or use a server that shares the HTTP server with Socket.IO.

Next.js rewrites (Chat + Socket.IO)

Browser code should call /api/chat/.... Use rewrites so the Next dev server forwards to your API. Add a second rule for /socket.io if the client connects through the app origin.

next.config.ts
// next.config.ts — Chat REST + Socket.IO through dev server (example) import type { NextConfig } from 'next'; const API = process.env.CHAT_DEV_API ?? 'http://localhost:3001'; const nextConfig: NextConfig = { async rewrites() { return [ { source: '/api/:path*', destination: API + '/:path*' }, { source: '/socket.io/:path*', destination: API + '/socket.io/:path*' }, ]; }, }; export default nextConfig;

Environment variables (frontend)

VariableRole
NEXT_PUBLIC_CHAT_DEV_USER_IDMust match the API “current user” (same value as X-User-Id header and server default, e.g. dev-user-local). Name it however you like in your own apiUrl helper.
NEXT_PUBLIC_API_URLOptional production API base. If empty, use same-origin /api/... (rewrites/proxy in dev).
NEXT_PUBLIC_SOCKET_ORIGINOptional; default window.location.origin so Socket.IO goes through the app dev server.

API + Socket helpers (you implement)

Add small modules in your repo, e.g. src/lib/api.ts with fivFoldUserId(), fivFoldAuthHeaders() (X-User-Id), and apiUrl(path). Use socket.io-client with io(`${origin}/chat`, { path: '/socket.io', query: { userId } }) — namespace /chat and path /socket.io must match the Nest gateway.

Integration host (you implement)

One component (e.g. src/integration/fivfold-chat-host.tsx) should load conversations/messages from REST, map DTOs to kit types, pass callbacks that fetch the Chat API, and subscribe to message:new / typing events. If list endpoints omit participants, synthesize minimal participant rows for avatars or extend your API.

TypeScript strict / shadcn quirks

  • Stray @ folder: If shadcn created @/components/... on disk, ensure components.json aliases match tsconfig paths. FivFold's add command attempts to merge misplaced files into src/components/ui.
  • noUnusedLocals: Strict projects may need relaxed rules for generated kit folders, or enable incremental cleanup.
  • Dialog close button: Newer shadcn uses showCloseButton; the kit templates use that API.

6Third-party integrations

The Chat Kit UI integrates with external services for GIFs, file uploads, and location sharing. Configure these in your frontend; the API supports the corresponding message types (see API tab).

Tenor GIF integration

Tenor (by Google) provides a GIF search API. The gif-picker.tsx component calls Tenor directly for search; when the user selects a GIF, it sends a message via onSendMessage with type: "gif" and metadata: { tenorId, url, previewUrl, ... }.

Flow: (1) User opens GIF picker → frontend calls Tenor Search API with query. (2) User selects GIF → call onSendMessage(convId, { type: "gif", metadata: { tenorId, url, previewUrl, description } }). (3) Your backend receives POST /api/chat/conversations/:id/messages and creates the message. (4) Optionally call Tenor's Register Share endpoint when a GIF is sent to improve future search results.

Tenor Search
// gif-picker.tsx — Tenor Search (frontend) const res = await fetch( `https://tenor.googleapis.com/v2/search?q=${query}&key=${TENOR_API_KEY}&client_key=my_app&limit=8&media_filter=tinygif,gif` ); const data = await res.json(); // Use tinygif for previews, gif for the URL sent in metadata

Setup: Create an API key in Google Cloud Console and enable the Tenor API. Use NEXT_PUBLIC_TENOR_API_KEY for client-side search, or proxy Tenor through your backend. Include client_key in all requests. Tenor requires attribution — see their attribution guide.

File storage (attachments)

The attachment-picker.tsx lets users select files. Attachments are stored in object storage — not in your database. Your frontend must upload the file first, then call onSendMessage (or your attachment endpoint) with the resulting URL.

  1. User selects file → upload to S3, GCS, R2, or your backend proxy (presigned URLs, etc.).
  2. On upload complete → call your API with url, name, size, mimeType.
  3. Backend creates Message + Attachment; UI displays via message-bubble.tsx.

Recommended: AWS S3, Google Cloud Storage, Cloudflare R2, or Azure Blob. Generate thumbnails for videos and pass thumbnailUrl for faster rendering.

Location sharing

The location-picker.tsx and location-display.tsx components handle sharing and display. Use navigator.geolocation.getCurrentPosition() to obtain coordinates, or allow manual input. Send via onSendMessage with type: "location" and metadata: { latitude, longitude, label? }.

No external API required. location-display.tsx can use OpenStreetMap for the preview and link to Google Maps for "Open in Maps".

7Shadcn primitives dependencies

npx @fivfold/ui add chat runs shadcn add for the list below. If anything is missing, run the same command manually from the project root where components.json lives so paths resolve (see troubleshooting in full-stack integration).

PrimitiveUsed for
dialogNew chat, search, GIF, location, polls, attachments
tabsTabbed dialogs
separatorLayout dividers
scroll-areaScrollable lists
inputText fields
textareaComposer
labelForm labels
badgeThread metadata
tooltipToolbar hints — wrap the app in TooltipProvider
dropdown-menuThread row ⋯ menu, sidebar overflow, chat header ⋯ menu
context-menuThread / message actions (right-click)
alert-dialogDestructive confirms (e.g. leave group)
switchPoll options
progressPoll UI
buttonEverywhere

8Additional dependencies

Besides shadcn/ui primitives, npx @fivfold/ui add chat adds these npm packages (declared in the kit manifest ui/manifests/chat.kit.json):

  • framer-motion — layout animations (typing indicator, message transitions).
  • date-fns — message timestamps and relative time.
  • emoji-mart — emoji picker for reactions and compose.
  • socket.io-client — optional real-time layer when you wire the kit to a Socket.IO backend (see integration section).

Search docs

Search documentation by title or keywords