Odin Book 最终项目实战:用 Node.js、Express 与 PostgreSQL 从零构建社交媒体网站
【免费下载链接】curriculumThe open curriculum for learning web development项目地址: https://gitcode.com/GitHub_Trending/cu/curriculum
本篇技术指南围绕仓库中 nodeJS/final_project/project_odin_book.md 这一 NodeJS 课程的收官项目展开。你将面对一份高度开放的高阶需求清单——克隆一个类似 Facebook、X、Myspace 或 Threads 的社交媒体网站,并完整实现用户、资料、帖子、关注、点赞、评论、认证等核心功能。读完本文,你将掌握"把高层面需求转化为可运行网站"的完整方法论:范围规划、数据建模、种子数据生成、Passport.js 认证接入、核心 CRUD 实现与 PaaS 部署,为简历添上一份重量级的全栈作品。
项目背景:为何这是课程的"毕业设计"
课程走到这一步,你已经具备独立构建 Express 应用、并用 PostgreSQL 建模和存储数据的能力。Odin Book 项目要求你把所有这些知识整合起来——它不是一道加试题,而是一次综合演练,难度可控但极具挑战,完成后会是一份极佳的简历作品。
从仓库课程结构看,这是 nodeJS 路径的最终项目,紧随其后的是 结课总结,后者明确写道:"你刚刚构建了一个社交媒体网站",并以此宣告你已具备"全栈开发者"的能力。这印证了该项目的定位:它是整个 NodeJS 课程的验收标准。
与课程中此前的项目相比,本项目有几个显著不同的特点:
- 开放性:需求是"全局性的功能清单",你需要自己决定克隆哪个平台、实现哪些功能、如何实现。
- 自主研究:你会用到几个此前未深入接触的模块,必须学会阅读官方文档。
- 数据复杂度:模型数量多、模型间关系复杂,远超你此前做过的任何项目。
在动手前请记住课程反复强调的原则:先把核心功能做完,再碰锦上添花的部分。如果试图一次做完所有事,你会迷失方向并感到挫败——一切都比预期耗时。
技术栈准备:回顾你已掌握的构建模块
本项目会用到你在 NodeJS 路径 中学过的几乎所有关键能力。在开工前,建议快速回顾以下课程的要点,它们将直接支撑本项目:
1. Express 应用骨架与路由
回顾 Express 入门:从npm init -y与npm install express起步,理解请求对象req、响应对象res、中间件链与res.send/res.render/res.redirect的差别。开发时可用node --watch app.js实现文件变更自动重启,端口建议按环境变量读取:const PORT = process.env.PORT || 3000;——这在部署阶段至关重要。
在 路由 中,你会掌握 HTTP 动词(GET/POST/PUT/DELETE)与路径匹配。本项目推荐遵循 MVC 模式组织代码:routes/(路由定义)、controllers/(控制器)、views/(视图模板),具体结构示例见 控制器课程:
express-app/ ├─ errors/ ├─ controllers/ │ ├─ usersController.js │ ├─ postsController.js ├─ routes/ │ ├─ usersRouter.js │ ├─ postsRouter.js ├─ views/ ├─ app.js ├─ db/ 或 prisma/控制器是"终极中间人":它知道要问模型什么问题、要渲染哪个视图,但把具体实现交给模型和视图。同时要记得把错误处理中间件(四个参数的(err, req, res, next))放在应用末尾,并用自定义错误类(如继承Error并携带statusCode)区分 404 与 500。
2. 数据库:PostgreSQL 建模与查询
你此前已经掌握两种与数据库交互的方式:
- 原生 SQL +
pg:见 使用 PostgreSQL。使用连接池new Pool({...})管理连接,查询务必使用参数化查询($1占位符)以防 SQL 注入。 - Prisma ORM:见 Prisma ORM 课程。Prisma 将数据库定义(schema)纳入代码库,通过
npx prisma migrate管理迁移、npx prisma generate生成类型安全的客户端,并用prisma.message.create({ data: {...} })这类 API 取代手写 SQL。
对 Odin Book 这种多模型、多关系的项目,ORM 的价值会充分体现:关系定义在 schema 中一目了然,迁移有标准化的 changelog,不再需要登录数据库才能理解代码结构。选择pg还是 Prisma 都可以,关键是先想清楚数据架构再动手。
3. 认证:Passport.js 全家桶
本项目明确要求实现某种形式的认证,首选 passport.js 中已经完整演练过以下流程:
- 中间件装配顺序:
express-session→passport.session()→express.urlencoded(); passport.use(new LocalStrategy(...))定义用户名密码校验逻辑,返回done(null, user)或done(null, false, { message });passport.serializeUser/passport.deserializeUser负责把用户 id 写入会话、再从数据库还原用户并挂到req.user;- 登录路由只需一行:
passport.authenticate("local", { successRedirect, failureRedirect, failureMessage: true }); - 登出用
req.logout((err) => {...}); - 密码必须用 bcrypt 哈希存储:注册时
bcrypt.hash(password, 10)(第二个参数为盐的轮数),校验时bcrypt.compare(password, user.password)。
本项目建议优先考虑第三方 OAuth 认证(见下文"认证方案选型"),但如果所选平台关闭了该途径,passport-local与passport-github2都是官方认可的替代方案。
4. 表单处理与安全
帖子、评论、关注请求、注册登录都涉及表单。回顾 表单与数据处理:
POST表单需要express.urlencoded({ extended: true })解析到req.body;- 用
express-validator的body()链式校验与清洗(.trim()、.notEmpty()、.isLength()等),再用validationResult(req)收集错误、matchedData(req)取清洗后的数据; - 遵守Post/Redirect/Get(PRG)模式:POST 处理后
res.redirect,避免重复提交; - 在模板中输出用户内容时使用 EJS 转义语法
<%= %>(而非<%- %>),防止 XSS 注入。
开工前规划:范围管理是成功的一半
原文档给出的第一条建议非常具体:把计划写在纸上或白板上。先花几个小时思考,能省下数天编码时间。规划的核心是scope(范围)——你显然无法重建整个网站(它可能是一个完整工程师团队花数年才做出来的产品),所以必须识别:
- 核心功能:用户注册登录、发帖、点赞、评论、关注、动态流、个人主页、用户列表;
- 锦上添花:图片上传、实时消息、通知、聊天等。
第二件事是想清楚数据架构:本项目模型众多、关系复杂,远超以往任何项目。建议在动手前完成以下设计:
User:用户名、密码哈希、资料(简介、头像 URL 等)、关注关系(自关联多对多);Post:内容、作者(外键)、时间戳;Like/Comment:分别关联用户与帖子;Follow或FriendRequest:记录关注/待处理请求的状态。
如果使用 Prisma,这些关系会以@relation(fields: [authorId], references: [id])的形式显式声明在 schema 中(示例见 Prisma ORM 课程 的Message/User模型),让整张数据模型图在代码库里一目了然。
用 Faker 生成种子数据:seeds.js
真实开发中,手工创建用户、帖子来测试界面既痛苦又低效。原文档给出的方案是使用Faker(faker-js/faker)模块:创建一个名为seeds.js的 JavaScript 文件,导入你的 Prisma 模型(如果使用 Prisma),用 faker 批量生成并保存新用户。
这与你在 使用 PostgreSQL 中学过的"用脚本填充数据库"一脉相承——只是把硬编码的 SQL 换成了 ORM 调用与随机数据。一个可参考的骨架:
// seeds.js —— 使用 Prisma 时 import { PrismaClient } from "@prisma/client"; import { faker } from "@faker-js/faker"; const prisma = new PrismaClient(); async function main() { for (let i = 0; i < 20; i++) { await prisma.user.create({ data: { username: faker.internet.username(), email: faker.internet.email(), password: faker.internet.password(), bio: faker.person.bio(), }, }); } console.log("seeding done"); } main().finally(async () => { await prisma.$disconnect(); });种子脚本应当"一次运行即可重建演示环境",方便你在开发、演示与部署后快速填充数据。注意密码字段即使是用 faker 生成的随机值,也建议走一遍 bcrypt 哈希流程,保持与真实注册路径一致。
核心需求清单:逐项实现
原文档提供了一份"非常全局"的功能清单。由于项目开放性,并非所有条目都适用于你选定的平台,也可能存在清单未提及但属于该平台核心的功能。以下是 11 项核心需求及其实现要点:
1. 除登录页外,用户必须登录才能看到任何内容
在 Express 中,用中间件保护所有非认证路由。参考 认证基础 中自定义中间件的思路:
function ensureAuthenticated(req, res, next) { if (req.isAuthenticated()) { return next(); } res.redirect("/log-in"); } app.use("/posts", ensureAuthenticated); app.use("/users", ensureAuthenticated);更优雅的做法是把当前用户写入res.locals.currentUser(一个位于 passport 中间件之后、路由之前的应用级中间件),这样所有视图都能直接访问登录用户,无需在控制器里逐个传递。
2. 用户能通过你选择的认证方式登录
见下方"认证方案选型"一节。无论选哪种策略,登录成功后都应重定向到动态流页面,失败则回到登录页并显示错误消息(failureMessage: true后通过req.session.messages读取)。
3. 用户可以向其他用户发送关注请求
这需要一个记录"发起者 → 目标用户 → 状态(pending/accepted)"的关联模型。你的用户索引页应只对"未关注、且没有待处理请求"的用户显示关注按钮——这要求查询时排除已关注者与已发请求者。
4. 用户可以创建帖子(先只支持纯文本)
最小可行版是一个Post模型(内容 + 作者 + 时间戳)加一个发帖表单。表单 POST 到控制器,经express-validator校验后写入数据库,再按 PRG 模式重定向回动态流。
5. 用户可以点赞帖子
点赞是"用户-帖子"的多对多关系。需要注意幂等性:同一用户对同一帖子只能有一个赞,重复点击应取消点赞(toggle)或禁止重复创建。可以在数据库中为(userId, postId)建立唯一约束来兜底。
6. 用户可以评论帖子
Comment模型关联用户与帖子。评论表单紧跟在帖子详情之下,与点赞一样需要校验与防 XSS 转义。
7. 帖子应始终显示内容、作者、评论与点赞
这是查询层面的要求:获取帖子时必须"预加载"(eager load)作者、评论与点赞计数。Prisma 中对应include:
const posts = await prisma.post.findMany({ include: { author: true, comments: true, likes: true, }, orderBy: { createdAt: "desc" }, });原生 SQL 则对应JOIN与聚合查询。视图渲染时统一展示这四个要素,是保持体验一致的基础。
8. 帖子索引页:显示当前用户及其关注者的近期帖子
这是社交平台的核心——"动态流"(feed)。查询条件为:帖子作者 ∈(当前用户 ∪ 当前用户已接受的关注对象)。这是本项目最复杂的查询之一,也是数据建模是否合理的最好检验。
9. 用户可以创建带头像的资料页
资料页包含资料信息、头像与帖子列表。头像来源取决于认证方式:
- 若用
passport-github2认证,可直接复用 GitHub 账号的现有头像; - 否则可使用 Gravatar(通过邮箱生成头像);
- 也可以在注册表单中直接提供头像 URL 字段。
10. 用户索引页:展示所有用户,并显示关注按钮
遍历所有用户,对每个用户根据当前登录状态决定渲染什么:
- 已关注 → 显示"已关注"(可提供取消关注);
- 有待处理请求 → 显示"请求已发送";
- 未关注且无请求 → 显示"发送关注请求"按钮;
- 自己 → 不显示任何按钮。
11. 部署到你选择的托管平台
见下方"部署上线"一节。
认证方案选型:OAuth 与本地策略
原文档明确建议:理想情况下用 passport.js 支持"通过你所克隆的社交平台进行认证"(即 OAuth 单点登录)。但部分平台(例如 Facebook)近期已让这一流程变得不可行。如果遇到这种情况,官方认可的备选方案有两个:
| 方案 | 适用场景 | 说明 |
|---|---|---|
passport-local | 通用、最基础 | 用户名 + 密码,配合 bcrypt 哈希存储,参见 认证基础 的完整实现 |
passport-github2 | 有 GitHub 账号的用户 | OAuth 流程,用户授权后回调,可直接复用其 GitHub 头像作为资料头像 |
选择passport-local时,把 认证基础 中演示的单文件结构拆分到routes/、controllers/与独立的认证配置模块中。如果希望会话持久化到数据库(而非内存),可参考该课作业中提到的connect-pg-simple(PostgreSQL)或prisma-session-store(Prisma,见 文件上传项目 的用法)——注意会话表不会自动创建,需按文档手动建表。
进阶功能:超出清单的部分
原文档明确指出,聊天、实时更新、通知等"你可能没接触过的功能"不属于责任范围——除非你对当前水平非常有信心。例如socket.io可以利用 WebSocket 实现实时通信。这些功能建议放到核心功能全部完成后,作为挑战项或加分项处理。
如果你决定冲刺这些功能,仓库中的 文件上传项目 提供了可迁移的思路:先用 multer 处理上传,再把文件交给 Cloudinary 或 Supabase Storage 托管,数据库只存 URL 而非二进制数据。
额外加分项(Extra credit)
核心功能完成后,原文档提供了四条加分建议,按性价比排序:
- 让帖子支持图片(URL 或上传)。参考 文件上传项目:Cloudinary 与 Supabase Storage 都是托管用户上传图片的好选择,它们返回的 URL 存入数据库即可。这也是你在该项目中学过的模式。
- 允许用户更新自己的头像——资料编辑页加一个头像字段。
- 访客登录功能:允许访客不经注册直接进入。这对求职简历上的项目尤其重要——大多数招聘官、HR 不会花时间注册账号。这个功能让评审者绕过繁琐注册流程直接体验你的作品。
- 美化界面:投入多少精力做样式完全由你决定,但一个整洁的界面会让作品完成度显著提升。
部署上线
动态网站需要能运行 Node.js 服务端代码和数据库的托管平台,GitHub Pages 这类静态托管无法胜任(详见 部署课程)。推荐的 PaaS 方案组合(按该课程的建议):
- Railway:可同时部署服务器与数据库,按用量付费,有一次性免费额度;
- Render:可部署服务器与数据库,每月有免费运行时长,应用闲置 15 分钟后会自动休眠;
- Neon / Aiven:专门托管数据库的服务,均提供无需信用卡的免费层级(PostgreSQL)。
部署时注意:
- 端口:使用
process.env.PORT,托管平台会自动注入; - 环境变量:数据库连接串、会话密钥等凭据绝不能硬编码在代码里,通过环境变量注入;
- Node 版本:可在
package.json的engines字段声明兼容版本,防止平台默认版本与你的代码不兼容; - 排错:部署失败先查构建日志,部署成功后遇到 500 页则查应用日志并复现问题;如果最新部署破坏了原本正常的应用,用
git log与git checkout回退到上一个可用版本,再逐步恢复变更。
部署完成后,把线上地址分享出去——这是你整个 NodeJS 学习阶段的成果展示。
结语:从需求到上线,你已具备全栈能力
Odin Book 是你把"相对高层的需求集"转化为"可用网站"的完整演练。它检验的不只是编码能力,更是规划能力、范围管理能力和自主查阅文档的能力。正如 结课总结 所说:完成这个项目后,你构建的 Node.js 应用已经具备现实世界真正有用的功能,可以把自己视为一名"全栈开发者"了。
动手之前,再读一遍原文档最朴实也最重要的忠告:先把计划写下来,先完成核心功能,再谈其他。祝编码顺利。
【免费下载链接】curriculumThe open curriculum for learning web development项目地址: https://gitcode.com/GitHub_Trending/cu/curriculum
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考