
实时聊天应用全解析SpacetimeDB LLM 基准套件中 TypeScript PostgreSQL Socket.io 的 Chat App 架构与实战【免费下载链接】SpacetimeDBDevelopment at the speed of light项目地址: https://gitcode.com/GitHub_Trending/sp/SpacetimeDB本文以 SpacetimeDB LLM 基准套件tools/llm-oneshot中由 AI 生成的chat-app-20260128-112222示例为对象完整讲解这套基于 TypeScript、PostgreSQL、Node.js、Express、Socket.io 与 React 构建的实时聊天应用从功能清单、项目结构、数据库设计到完整的 Socket 事件协议与部署流程。读完本文你将掌握一个可运行的全栈实时聊天系统的骨架如何用 Drizzle ORM 设计 11 张业务表、如何用 Socket.io 实现房间、消息、输入中、已读回执、表情回应、定时消息与阅后即焚等能力以及如何从零启动前后端并进行生产构建。项目定位与功能清单该示例位于 tools/llm-oneshot/apps/chat-app/typescript/grok-code/postgres/chat-app-20260128-112222是 LLM 基准测试oneshot在TypeScript PostgreSQL技术栈下生成的聊天应用产物。它不依赖 SpacetimeDB 运行时而是采用经典的三层结构Express 提供 REST 接口、Socket.io 提供全双工实时通道、PostgreSQL 通过 Drizzle ORM 承载持久化。基础聊天功能用户认证与显示名通过authenticate事件注册/登录仅凭显示名即可创建或复用用户记录创建与加入聊天房间支持create_room/join_room加入者自动成为房间成员实时消息收发通过send_message/new_message完成即时广播在线用户跟踪连接与断开时广播user_online/user_offline消息校验与垃圾信息防护服务端对显示名、房间名、消息内容做长度与空值校验。进阶功能输入中提示Typing Indicatorsstart_typing/stop_typing并在 3 秒无操作后自动过期已读回执Read Receiptsmark_as_read写入阅读记录并广播message_read未读消息计数Unread Counts房间列表展示未读角标广播unread_count_updated定时消息Scheduled Messagessend_message携带scheduledFor由后台任务到点投递阅后即焚消息Ephemeral Messagessend_message携带expiresAt到期后软删除并广播message_deleted表情回应Message Reactions支持 ❤️ 五种 emoji 的添加/移除与实时聚合消息编辑Message Editing仅作者可编辑编辑前的旧内容写入message_edits表形成历史。项目目录结构仓库内的实际目录布局与 README 描述一致前后端完全分离chat-app-20260128-112222/ ├── server/ # 后端 Node.js/Express 服务 │ ├── src/ │ │ ├── db/ │ │ │ ├── index.ts # Drizzle 数据库连接与 schema 导出 │ │ │ └── schema.ts # 11 张表的 Drizzle 定义 │ │ ├── services/ │ │ │ ├── messageCleanup.ts # 过期阅后即焚消息清理服务 │ │ │ └── scheduledMessages.ts # 定时消息投递服务 │ │ ├── socket/ │ │ │ └── index.ts # 全部 Socket.io 事件处理器 │ │ └── index.ts # Express Socket.io 服务入口 │ ├── drizzle.config.ts # Drizzle Kit 迁移配置 │ ├── package.json │ └── tsconfig.json ├── client/ # 前端 React 应用 │ ├── src/ │ │ ├── components/ # Login/Chat/RoomList/MessageList 等组件 │ │ ├── App.tsx # 全局状态与 Socket 事件绑定 │ │ ├── main.tsx │ │ └── types.ts # TypeScript 接口定义 │ ├── package.json │ ├── tsconfig.json │ └── vite.config.ts ├── GRADING_RESULTS.md # LLM 基准评分结果 └── README.md完整路径从仓库根目录展开为 server 与 client 两个子目录均自带独立的package.json与tsconfig.json可分别安装、构建与运行。环境要求与快速启动前置条件Node.js 18服务端脚本dev使用tsx watch前端使用 Vite 5均要求较新的 Node 版本PostgreSQL 12示例默认连接串为postgresql://postgres:postgreslocalhost:5432/chat-appnpm 或 yarn。数据库初始化在 PostgreSQL 中创建名为chat-app的数据库如需修改连接信息更新 server/.env 中的DATABASE_URL。若不设置代码会回退到默认本地连接串——这一逻辑同时出现在 db/index.ts 与 drizzle.config.ts 两处务必保持一致。后端启动cd server npm install npm run db:generate # 根据 schema.ts 生成迁移文件 npm run db:migrate # 应用迁移建表 npm run dev # 以 tsx watch 模式启动开发服务器三个脚本定义在 server/package.json 中db:generate调用drizzle-kit generate、db:migrate调用drizzle-kit migrate另有一个未在 README 中强调的db:pushdrizzle-kit push:pg可用于跳过迁移文件直接把 schema 推送到数据库。前端启动cd client npm install npm run dev # Vite 开发服务器运行应用先启动后端默认监听3001端口再启动前端Vite 默认监听3000端口浏览器打开http://localhost:3000即可使用。前后端的端口约定由代码显式指定服务端const PORT process.env.PORT || 3001Socket.io 的 CORS 白名单默认http://localhost:3000见 server/src/index.ts前端在 client/src/App.tsx 中通过io(http://localhost:3001)建立连接。后端架构Express Socket.io Drizzle ORM服务入口与中间件server/src/index.ts 是本服务的总装线创建 Express 应用与原生 HTTP server挂载helmet安全头、cors跨域、express.json()JSON 解析三层中间件提供/health健康检查接口返回{ status: ok, timestamp }调用setupSocketHandlers(io)注册全部 Socket 事件通过setSocketServer(io)/setCleanupSocketServer(io)把 Socket 实例注入两个后台服务启动两个定时器每 30 秒清理过期消息、每 10 秒投递到期定时消息监听PORT默认 3001。值得注意虽然package.json中声明了bcrypt、jsonwebtoken、pg等依赖但当前实现的实际认证是按显示名查找/创建用户的简化方案并未使用密码哈希或 JWT这正是基准测试产物的特点——依赖清单比实际使用面更广。数据连接层db/index.ts 使用postgrespostgres.js驱动 drizzle-orm/postgres-js适配器prepare: false关闭预编译以兼容原生 SQL 片段。它读取DATABASE_URL环境变量导出统一的db实例并 re-export 全部 schema 表供 socket 处理器与后台服务引用。后台服务定时投递与过期清理定时消息投递services/scheduledMessages.ts每 10 秒查询scheduled_messages中scheduled_for now()且status pending的记录通过innerJoin messages、leftJoin users组装完整消息用io.to(roomId).emit(new_message, ...)广播并标记status sent防止重复投递。过期消息清理services/messageCleanup.ts每 30 秒找出expires_at now()且未被删除的消息执行软删除将is_deleted置为true保留数据、便于审计逐个房间广播message_deleted前端收到后立即从消息列表移除对应消息。这两个服务通过setSocketServer/setCleanupSocketServer获得 Socket 实例属于轮询 广播的实现模式架构简单、易于理解适合中小规模实时场景。数据库设计11 张业务表详解db/schema.ts 使用 Drizzle 的pg-core定义了完整的关系模型主键统一采用uuid并defaultRandom()表名核心字段说明usersid、display_name、created_at、last_seen用户账号显示名唯一复用roomsid、name、created_by(→users)、is_private聊天房间is_private预留room_membersroom_id、user_id、last_read_message_id房间成员关系含(room_id, user_id)联合索引messagesroom_id、user_id、content、expires_at、scheduled_for、is_deleted消息主表含 3 个查询索引message_editsmessage_id、previous_content、edited_by编辑历史逐条保留旧内容message_reactionsmessage_id、user_id、emoji表情回应(message_id, user_id)联合索引read_receiptsmessage_id、user_id、read_at已读回执明细typing_indicatorsroom_id、user_id、expires_at输入中状态联合唯一索引防重复unread_countsroom_id、user_id、count、updated_at未读计数表用表替代计算视图以换取性能scheduled_messagesmessage_id、scheduled_for、status定时消息队列status ∈ {pending, sent, cancelled}online_usersuser_id、socket_id、last_ping在线用户跟踪socket 级粒度几个值得注意的设计细节messages表通过expires_at、scheduled_for两个可空时间戳同时支撑阅后即焚与定时消息用is_deleted布尔字段实现软删除且对(room_id, created_at)、scheduled_for、expires_at分别建索引以加速消息拉取与后台轮询typing_indicators与unread_counts使用uniqueIndex保证每个用户在每个房间只有一条记录配合onConflictDoUpdate实现 upsertunread_counts是典型的以写换读权衡——README 注释明确说明用表代替计算视图是为了查询性能见 schema.ts。Socket.io 事件协议全览所有事件处理器集中在 socket/index.ts。事件命名统一采用snake_case动词 过去分词回执的约定客户端与服务端一一对应。箭头→表示客户端→服务端emit←表示服务端→客户端广播/回执。认证Authenticationauthenticate→{ displayName: string }服务端校验显示名非空且 ≤ 50 字符按名查找或创建用户写入online_users随后authenticated←{ userId: string, displayName: string }回执认证结果同时广播user_online并向该 socket 推送initial_data我的房间、在线用户、未读计数。房间管理Room Managementcreate_room→{ name: string }校验房间名 ≤ 100 字符建房间并把创建者加入room_members向全体在线用户广播room_created←{ roomId: string, name: string }join_room→{ roomId: string }校验房间存在幂等加入成员socket.join(roomId)后回传最近 100 条消息room_joined←{ roomId: string, messages: Message[] }另有 README 未展开但代码中存在的get_all_rooms→all_rooms返回带成员数的房间列表。消息收发Messagingsend_message→{ roomId, content, scheduledFor?, expiresAt? }校验内容非空且 ≤ 2000 字符且发送者必须是房间成员校验scheduledFor/expiresAt为合法日期字符串isNaN检查立即消息直接向房间广播new_message←Message定时消息写入scheduled_messages队列回执message_schedulededit_message→{ messageId: string, content: string }仅消息作者可编辑旧内容先写入message_edits再更新主表广播message_edited←{ messageId, content, updatedAt }。实时功能Real-time Featuresstart_typing→{ roomId }写入/刷新 typing 记录有效期 3 秒expiresAt now 3000通过onConflictDoUpdate幂等更新然后user_typing←{ userId, displayName, roomId }stop_typing→{ roomId }删除记录并广播user_stopped_typing←{ userId, roomId }mark_as_read→{ roomId, messageId }写入read_receiptsonConflictDoNothing去重更新room_members.last_read_message_id重算未读数并广播message_read←{ messageId, userId, displayName }add_reaction/remove_reaction→{ messageId, emoji }服务端校验 emoji 必须在[, ❤️, , , ]白名单内每次变更后用count(*)array_agg(display_name)按 emoji 聚合广播reaction_updated←{ messageId, reactions: Reaction[] }。状态更新Status Updatesuser_online←{ userId, displayName }认证成功后全量广播user_offline←{ userId }断开连接时删除online_users与 typing 记录后广播unread_count_updated←{ roomId, count }新消息到达时对房间内其他成员重算未读数并广播。未读计数是一个值得留意的实现updateUnreadCountssocket/index.ts按用户参与的每个房间遍历以last_read_message_id对应消息的created_at为界统计is_deleted false的消息数再 upsert 进unread_counts。代码注释也承认这是简化实现生产环境需要更精细的策略。前端架构React Vite socket.io-client全局状态与事件绑定client/src/App.tsx 是前端唯一的状态中枢用一个useEffect建立 Socket 连接并注册全部服务端事件另一个useEffect集中绑定authenticated、initial_data、room_created、room_joined、new_message、message_edited、message_deleted、user_online/offline、user_typing/stopped_typing、reaction_updated、unread_count_updated、error等监听并在清理函数中逐一socket.off防止内存泄漏。所有发消息的操作authenticate、createRoom、joinRoom、sendMessage、editMessage、startTyping、stopTyping、markAsRead、addReaction、removeReaction封装为可供子组件调用的回调函数通过 props 逐层下发到 components 目录中的Login、Chat、RoomList、MessageList、MessageInput、OnlineUsers、CreateRoomModal等组件。客户端类型契约client/src/types.ts 定义了与后端消息结构严格对齐的接口User、Room、Message含可选的isDeleted、expiresAt、reactions、Reactionemoji count users、OnlineUser、UnreadCount、TypingUser。这些类型与 README 的 Socket 协议描述一一对应是前后端联调的类型契约。前端依赖见 client/package.jsonreact18、socket.io-client4.7.5、date-fns时间格式化、lucide-react图标库构建工具为vite5。开发工作流与生产构建数据库迁移cd server npm run db:generate # 从 schema.ts 生成迁移文件到 ./drizzle npm run db:migrate # 应用迁移迁移目标目录与驱动在 drizzle.config.ts 中配置out: ./drizzle、driver: pg开发期也可用npm run db:push直接推送 schema。生产构建# 后端 cd server npm run build # tsc 编译到 dist/ npm start # node dist/index.js # 前端 cd client npm run build # tsc vite build产出静态资源 npm run preview # 本地预览构建产物在 LLM 基准套件中的定位本应用是 SpacetimeDB LLM oneshot 基准tools/llm-oneshot/apps/chat-app/prompts的测试产物之一仓库中并存着相同提示词在不同技术栈TypeScriptPostgreSQL、TypeScriptMongoDB、TypeScriptSpacetimeDB、Rust/C#SpacetimeDB与不同模型grok-code、gpt-5-2、gemini-3-pro、opus-4-5 等下的多份实现本目录旁的 GRADING_RESULTS.md 记录了对应评分。因此这份 README 与源码既是一份可运行的完整聊天应用参考实现也作为评估 LLM 生成代码质量功能完整度、架构合理性、安全性的对照样本其简化认证、轮询后台任务、表替代计算视图等取舍正体现了基准产物在演示性与生产级之间的平衡。总结从这份示例可以提炼出一套可复用的实时聊天系统参考模板Drizzle ORM 一次性定义关系型 schema →drizzle-kit管理迁移 → Express 提供 REST 与中间件 → Socket.io 承载全部实时事件 → 后台定时任务补齐定时消息与过期清理 → React 前端以集中式 Socket 事件绑定完成状态同步。其事件协议snake_case动词 回执命名与消息软删除 upsert 计数 白名单 emoji等实现细节都值得在类似项目IM、协作白板、直播间互动中直接借鉴。【免费下载链接】SpacetimeDBDevelopment at the speed of light项目地址: https://gitcode.com/GitHub_Trending/sp/SpacetimeDB创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考