首页
/ freeCodeCamp Advanced Node and Express 项目实战:实现 Passport 用户序列化并连接 MongoDB 数据库

freeCodeCamp Advanced Node and Express 项目实战:实现 Passport 用户序列化并连接 MongoDB 数据库

2026-09-04 12:12:20作者:温玫谨Lighthearted

本篇基于 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...

这段代码有三个要点:

  1. myDataBase 的获取被移入回调client.db('database').collection('users') 只在连接成功后执行,users 集合从此才是真实可用的数据源,供 deserializeUser 查询。
  2. 路由注册延后:首页路由渲染 index 模板,标题改为 Connected to Database、消息为 Please login——这既是功能性的状态提示,也是课程测试的断言目标(见第五节)。
  3. 错误兜底分支.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 中的用户 _idusers 集合执行 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*nullrequire('mongodb')new ObjectID(id)),可以看出课程刻意让两课形成"先占位、后接通"的渐进路径。

六、小结与在本项目中的位置

本课是 freeCodeCamp 微服务认证项目(Advanced Node and Express,对应课程结构文件 advanced-node-and-express.json)认证部分的关键一跳,完成后你应当具备以下能力:

  1. 使用 MONGO_URI 环境变量为 Node 应用配置 MongoDB 持久连接,并由 connection.js(封装的 myDB)管理连接生命周期;
  2. 通过 myDB(callback).catch(...) 的结构,实现"连接成功才注册业务路由、连接失败则降级提示"的启动顺序控制;
  3. 让 Passport 的 deserializeUser 基于 users 集合做真实查询,用 done(null, doc) 将会话 key 还原为完整用户对象。

完成本课后再提交页面,若遇到报错,原文档指向了课程论坛中「项目进展到此为止」的参考帖子(#implement-the-serialization-of-a-passport-user-5 锚点)。后续课程将继续在此基础上引入认证策略(Local Strategy / Google OAuth)与自定义中间件,认证闭环也就此逐步成型。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
903
1.82 K
docsdocs
暂无描述
Markdown
888
5.78 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
527
590
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.51 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.33 K
1.45 K
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384
flutter_flutterflutter_flutter
本仓库是 Flutter SDK 与 Flutter Engine 的 OpenHarmony 适配版本,由 CPF-Flutter 团队维护。开发者可使用熟悉的 Flutter 技术栈开发 OpenHarmony 应用,3.35.7 及以后的适配版本可基于本仓库源码构建支持 OpenHarmony 的 Flutter Engine。
Dart
1.17 K
341