freeCodeCamp Advanced Node and Express 项目实战:实现 Passport 用户序列化并连接 MongoDB 数据库
本篇基于 freeCodeCamp 课程仓库中「Advanced Node and Express」项目的第 5 课 Implement the Serialization of a Passport User(挑战 ID:5895f70cf9fc0f352b528e67),讲解如何把此前占位的 Passport 序列化逻辑升级为真实数据库实现:通过 MONGO_URI 环境变量建立持久数据库连接,用 myDB 包装序列化与路由代码以保证先连库后服务,并在 deserializeUser 中通过 done(null, doc) 返回完整的用户对象。读完后你将掌握 Node + Express + Passport + MongoDB 这套认证栈中「会话恢复」的完整落地方式。
一、背景:为什么这一课要"接通数据库"
在认证系统中,序列化(serialization)与反序列化(deserialization)是一对核心概念:把一个对象转换成可以随 Cookie 传递的小型 key,并在后续请求中由该 key 反解出原始对象,从而让服务器无需每个请求都接收用户名/密码即可识别会话归属。这一点在上一课「Serialization of a User Object」中已经建立(见 5895f70cf9fc0f352b528e66.md):
passport.serializeUser((user, done) => {
done(null, user._id);
});
passport.deserializeUser((id, done) => {
myDataBase.findOne({ _id: new ObjectID(id) }, (err, doc) => {
done(null, null);
});
});
上一课为了绕开"数据库尚未连接"的问题,要求把 myDataBase.findOne 注释掉、暂时只调用 done(null, null)。本课(5895f70cf9fc0f352b528e67.md)正是补上这一环:让 deserializeUser 真正从数据库中查询并返回用户文档。从 curriculum/structure/blocks/advanced-node-and-express.json 中的 challengeOrder 可以看到,本课位于「Set up Passport」「Serialization of a User Object」之后、「Authentication Strategies」之前,是整个微服务项目中认证体系的承上启下环节。
二、第一步:通过 MONGO_URI 配置数据库连接串
由于此前的代码并没有加载真实用户对象(数据库尚未配置),本课的第一步是建立一个贯穿应用整个生命周期的持久连接:在启动服务器时连接一次数据库,并保持连接直到应用关闭。具体做法是:
- 将你的数据库连接串(例如:
mongodb+srv://<username>:<password>@cluster0-jvwxi.mongodb.net/?retryWrites=true&w=majority)写入环境变量MONGO_URI; - 该变量会被项目中的
connection.js文件读取使用。
原文档同时提示:如果在 MongoDB Atlas 上搭建免费数据库时遇到问题,可参考官方教程《Get started with MongoDB Atlas》(原文以外部链接形式给出,此处不再转述链接)。注意这个连接串包含账号密码,属于敏感凭据,这也是本课使用环境变量而非硬编码的原因。
三、核心改动:用 myDB 包装序列化与路由,先连库后监听
连接数据库之后要开始监听请求,这样做的目的非常明确:不允许在数据库连接建立之前、或数据库出现错误时接收请求。为此,需要把序列化逻辑与应用路由包裹在 myDB 的回调中:
myDB(async client => {
const myDataBase = await client.db('database').collection('users');
// Be sure to change the title
app.route('/').get((req, res) => {
// Change the response to render the Pug template
res.render('index', {
title: 'Connected to Database',
message: 'Please login'
});
});
// Serialization and deserialization here...
// Be sure to add this...
}).catch(e => {
app.route('/').get((req, res) => {
res.render('index', { title: e, message: 'Unable to connect to database' });
});
});
// app.listen out here...
这段代码有三个要点:
myDataBase的获取被移入回调:client.db('database').collection('users')只在连接成功后执行,users集合从此才是真实可用的数据源,供deserializeUser查询。- 路由注册延后:首页路由渲染
index模板,标题改为Connected to Database、消息为Please login——这既是功能性的状态提示,也是课程测试的断言目标(见第五节)。 - 错误兜底分支:
.catch(e => ...)中注册了一个降级路由,一旦数据库连接失败,首页会直接显示错误信息与Unable to connect to database,而不是让服务器在半初始化状态下运行。同时注释// app.listen out here...提示app.listen应保持在回调与 catch 之外,保证无论连接成功与否,服务器最终都会监听端口。
四、反序列化收尾:取消注释并传入 doc
代码结构就位后,最后一步是让 deserializeUser 真正工作:
- 取消上一课中
myDataBase相关代码的注释; - 将
done(null, null)修改为携带查询结果:done(null, doc)。
即把上一课占位的写法改为:
passport.deserializeUser((id, done) => {
myDataBase.findOne({ _id: new ObjectID(id) }, (err, doc) => {
done(null, doc);
});
});
这样,Passport 在每次携带会话 Cookie 的请求到来时,都能用 Cookie 中的用户 _id 去 users 集合执行 findOne,并把完整的用户文档挂回 req.user,后续的路由(如展示 Profile 页)即可直接使用登录用户信息。ObjectID 类来自 mongodb 包(mongodb@~3.6.0,项目模板已预置该依赖),声明方式为 const { ObjectID } = require('mongodb');。
五、课程测试如何验证你的实现
该挑战属于 challengeType: 2(微服务项目),测试运行在课程平台上提供的浏览器工作区环境中,通过 fetch 读取你工作区内的文件与运行结果来做断言。本课(5895f70cf9fc0f352b528e67.md)内置了两条验收断言,恰好对应上述两步改动:
断言一:数据库连接存在——请求首页,页面文本必须匹配 /Connected to Database/gi:
const url = new URL("/", code);
const res = await fetch(url);
const data = await res.text();
assert.match(
data,
/Connected to Database/gi,
'You successfully connected to the database!'
);
断言二:反序列化必须使用数据库——拉取 /_api/server.js 源码,必须匹配 /null,\s*doc/gi,即 done(null, null) 已被改为 done(null, doc):
const url = new URL("/_api/server.js", code);
const res = await fetch(url);
const data = await res.text();
assert.match(
data,
/null,\s*doc/gi,
'The callback in deserializeUser of (null, null) should be altered to (null, doc)'
);
这揭示了课程测试的设计思路:一是运行时验证(首页渲染结果证明数据库连接成功且 myDB 回调生效),二是源码模式验证(静态检查反序列化是否真的把 doc 传回 done 回调)。对照上一课的断言(要求源码中存在 null,\s*null、require('mongodb') 与 new ObjectID(id)),可以看出课程刻意让两课形成"先占位、后接通"的渐进路径。
六、小结与在本项目中的位置
本课是 freeCodeCamp 微服务认证项目(Advanced Node and Express,对应课程结构文件 advanced-node-and-express.json)认证部分的关键一跳,完成后你应当具备以下能力:
- 使用
MONGO_URI环境变量为 Node 应用配置 MongoDB 持久连接,并由connection.js(封装的myDB)管理连接生命周期; - 通过
myDB(callback).catch(...)的结构,实现"连接成功才注册业务路由、连接失败则降级提示"的启动顺序控制; - 让 Passport 的
deserializeUser基于users集合做真实查询,用done(null, doc)将会话 key 还原为完整用户对象。
完成本课后再提交页面,若遇到报错,原文档指向了课程论坛中「项目进展到此为止」的参考帖子(#implement-the-serialization-of-a-passport-user-5 锚点)。后续课程将继续在此基础上引入认证策略(Local Strategy / Google OAuth)与自定义中间件,认证闭环也就此逐步成型。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0622
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00