d
验证与 ESLint
我们通常希望对存储在应用程序数据库中的数据应用一些约束。我们的应用程序不应接受 content 属性缺失或为空的笔记。现在笔记的有效性在路由处理函数中检查:
app.post('/api/notes', (request, response) => {
const body = request.body
if (!body.content) { return response.status(400).json({ error: 'content missing' }) }
// ...
})如果笔记没有 content 属性,我们就以 400 bad request 的状态码响应请求。
在数据存储到数据库之前验证数据格式的一种更智能的方法是使用 Mongoose 提供的验证功能。
我们可以为模式中的每个字段定义特定的验证规则:
const noteSchema = new mongoose.Schema({
content: { type: String, minLength: 5, required: true }, important: Boolean
})现在 content 字段要求至少五个字符长,并且被设为 required,意味着字段不能缺失。我们没有对 important 字段添加任何约束,所以它在模式中的定义没有改变。
minLength 和 required 验证器是 Mongoose 提供的内置的验证器。如果内置的验证器全都无法满足我们的需求,我们还可以用 Mongoose 的自定义验证器功能创建新的验证器。
如果我们试图在数据库中存储一个违反了某些约束的对象,操作将会抛出异常。让我们更改创建新笔记的处理函数,来将任何可能的异常传递给错误处理中间件:
app.post('/api/notes', (request, response, next) => { const body = request.body
const note = new Note({
content: body.content,
important: body.important || false,
})
note.save()
.then(savedNote => {
response.json(savedNote)
})
.catch(error => next(error))})让我们扩展错误处理函数来处理这些验证错误:
const errorHandler = (error, request, response, next) => {
console.error(error.message)
if (error.name === 'CastError') {
return response.status(400).send({ error: 'malformatted id' })
} else if (error.name === 'ValidationError') { return response.status(400).json({ error: error.message }) }
next(error)
}当对象验证失败时,我们返回以下 Mongoose 的默认错误消息:

将数据库后端部署到生产环境
该应用程序应该能在 Fly.io/Render 上按原样工作。由于到目前为止我们只对后端进行了修改,所以我们不需要构建前端的新生产版本。
dotenv 中定义的环境变量只会在后端不处于生产模式,即不在 Fly.io 或 Render 中时使用。
对于生产环境,我们需要在托管我们应用的服务中设置数据库 URL。
在 Fly.io 中,可以通过 fly secrets set 命令来完成:
fly secrets set MONGODB_URI='mongodb+srv://fullstack:thepasswordishere@cluster0.a5qfl.mongodb.net/noteApp?retryWrites=true&w=majority'在开发应用的过程中,很可能会出现一些失败的情况。例如,当我第一次部署带有数据库的应用时,一个笔记都没有看到:

浏览器控制台的网络标签页显示并未成功获取笔记,请求只是在 pending 状态下停留了很长时间,最后以 502 状态码失败。
必须始终打开浏览器控制台!
同时,持续关注服务端日志也非常重要。当我用 fly logs 打开日志时,问题就显而易见了:

数据库 URL 是 undefined ,所以是忘记执行 fly secrets set MONGODB_URI 命令了。
你还需要在 MongoDB Atlas 中将 fly.io 应用的 IP 地址添加到白名单中。否则 MongoDB 会拒绝连接。
遗憾的是,fly.io 不会给你的应用提供一个专门的 IPv4 地址,所以你需要在 MongoDB Atlas 中允许所有的 IP 地址。
在使用 Render 时,可以通过在仪表板中定义适当的环境变量来提供数据库 URL:

Render 仪表板显示服务端日志:

你可以在这个 GitHub 仓库的 part3-6 分支中找到我们当前应用的完整代码。
lint
在我们进入下一章节之前,我们来介绍一个重要的工具,叫做 lint。维基百科对 lint 的描述如下:
一般来说,lint 或 linter 是任何检测并标记编程语言中的错误,包括样式错误的工具。术语“lint 类行为”有时用于标记语言的可疑用法的过程。lint 类工具通常对源代码进行静态分析。
对于编译型静态类型语言,比如 Java,NetBeans 等 IDE 可以指出代码中的错误,甚至不止是编译错误。像 checkstyle 这样用于执行静态分析的附加工具,可以用来扩展 IDE 的能力,来指出与样式相关的问题,如缩进。
在 JavaScript 领域,目前领头的静态分析工具(又称“linting”)是ESlint。
让我们将 ESLint 添加为后端的开发依赖项。开发依赖项是只在开发应用的过程中需要的工具。比如,和测试有关的工具就是开发依赖项。当应用以生产模式运行时,就不需要开发依赖项了。
使用以下命令将 ESlint 作为开发依赖项安装到后端项目中:
npm install eslint @eslint/js --save-devpackage.json 文件的内容会这么变化:
{
//...
"dependencies": {
"dotenv": "^16.4.7",
"express": "^5.1.0",
"mongoose": "^8.11.0"
},
"devDependencies": { "@eslint/js": "^9.22.0", "eslint": "^9.22.0" }
}该命令在文件中添加了 devDependencies 一节,并在其中添加了 eslint 和 @eslint/js,同时在 node_modules 目录中安装了所需的库。
之后我们可以用以下命令初始化默认的 ESlint 配置:
npx eslint --init我们要回答所有的问题:

配置将会保存在 eslint.config.mjs 文件中。
格式化配置文件
让我们重设配置文件 eslint.config.mjs 的格式,将它从当前的格式改为:
import globals from 'globals'
export default [
{
files: ['**/*.js'],
languageOptions: {
sourceType: 'commonjs',
globals: { ...globals.node },
ecmaVersion: 'latest',
},
},
]到目前为止,ESLint 配置文件在 files 选项中定义了 ["**/*.js"],告诉 ESLint 要检查项目目录中的所有 JavaScript 文件。languageOptions 属性指定 ESLint 应支持的语言特性相关的选项,其中我们将 sourceType 设为“commonjs”。这表示我们项目中的 JavaScript 代码使用 CommonJS 模块系统,从而使 ESLint 能以相应的方法分析代码。
globals 属性指定预定义的全局变量。这里使用的展开运算符告诉 ESLint 包含 globals.node 设定中定义的所有全局变量,比如 process。对于浏览器端代码,我们会在这里定义 globals.browser,来允许浏览器特有的全局变量,比如 window 和 document。
最后,ecmaVersion 属性设为“latest”。这将 ECMAScript 版本设为最新可用版本,意味着 ESLint 能理解并正确检查最新的 JavaScript 语法和特性。
我们希望同时使用 ESLint 推荐的和自定义的设置。之前安装的 @eslint/js 包提供了 ESLint 的预定义配置。我们可以在配置文件中导入并启用:
import globals from 'globals'
import js from '@eslint/js'// ...
export default [
js.configs.recommended, {
// ...
},
]我们已将 js.configs.recommended 添加到配置数组的最上面,这确保 ESLint 的推荐设置会在我们自定义的选项之前先应用。
让我们继续构建配置文件。安装一个定义了一套与代码样式相关的规则的插件:
npm install --save-dev @stylistic/eslint-plugin-js导入并启用插件,并添加这四条代码样式规则:
import globals from 'globals'
import js from '@eslint/js'
import stylisticJs from '@stylistic/eslint-plugin-js'
export default [
{
// ...
plugins: { '@stylistic/js': stylisticJs, }, rules: { '@stylistic/js/indent': ['error', 2], '@stylistic/js/linebreak-style': ['error', 'unix'], '@stylistic/js/quotes': ['error', 'single'], '@stylistic/js/semi': ['error', 'never'], }, },
]plugins 属性提供了一种可以通过添加自定义规则、配置以及其他核心 ESLint 库中没有的功能来扩展 ESLint 功能的方法。我们已安装并启用了 @stylistic/eslint-plugin-js,它为 ESLint 添加了 JavaScript 样式的规则。此外,还添加了关于缩进、换行、引号和分号的规则,这四条规则都是在 Eslint styles plugin 中定义的。
Windows 用户注意事项:样式规则中将换行样式设为了 unix。建议无论使用什么操作系统都使用 Unix 样式的换行符(\n),这样文件可以兼容大多数现代操作系统,并且在多人处理同一文件时更方便协作。如果使用 Windows 样式的换行符,ESLint 会产生如下错误:Expected linebreaks to be 'LF' but found 'CRLF'。遇到这种情况时,按照这份指南将 Visual Studio Code 配置为使用 Unix 样式换行。
运行 linter
要检查和验证某个文件,比如 index.js,可以使用以下命令:
npx eslint index.js建议为 linting 创建一个专门的 npm 脚本:
{
// ...
"scripts": {
"start": "node index.js",
"dev": "nodemon index.js",
// ...
"lint": "eslint ." },
// ...
}现在,npm run lint 命令将检查项目中的每个文件。
当运行命令时,dist 目录中的文件也会被检查。我们不希望这种情况发生,我们可以通过添加一个对象,并在其 ignores 属性声明一个我们要忽略的目录和文件的数组来实现这一点。
// ...
export default [
js.configs.recommended,
{
files: ['**/*.js'],
// ...
},
{ ignores: ['dist/**'], },]这会使得整个 dist 目录都不被 ESlint 检查。
lint 对我们的代码有很多意见:

相比于从命令行执行 linter,更好的替代方案是给编辑器配置 eslint 插件,来持续不断地运行 linter。使用这个插件后,你将立即在代码中看到错误。你可以在这里找到更多关于 Visual Studio ESLint 插件的信息。
VS Code ESlint 插件会用红线划出违反样式的地方:

这会让错误很容易发现,从而立即修复。
添加更多样式规则
ESlint 有大量易于使用的规则,只要编辑 eslint.config.mjs 文件即可使用。
让我们添加 eqeqeq 规则,如果相等不是用三等号===检查的,eqeqeq 规则就会警告我们。将该规则添加到配置文件的 rules 字段下。
export default [
// ...
rules: {
// ...
eqeqeq: 'error', },
// ...
]让我们顺便对规则进行一些其他更改。
让我们阻止行尾的不必要的尾随空格,要求大括号前后始终有一个空格,并且也要求箭头函数的函数参数中一致使用空格。
export default [
// ...
rules: {
// ...
eqeqeq: 'error',
'no-trailing-spaces': 'error', 'object-curly-spacing': ['error', 'always'], 'arrow-spacing': ['error', { before: true, after: true }], },
]我们的默认配置使用了这里预定义的一串规则:
// ...
export default [
js.configs.recommended,
// ...
]这里包括一条警告 console.log 命令的规则,而我们不想使用这条规则。禁用一条规则可以通过在配置文件中定义其“value”为 0 或 off 来完成。让我们在此期间对 no-console 规则这样做。
[
{
// ...
rules: {
// ...
eqeqeq: 'error',
'no-trailing-spaces': 'error',
'object-curly-spacing': ['error', 'always'],
'arrow-spacing': ['error', { before: true, after: true }],
'no-console': 'off', },
},
]禁用 no-console 规则使我们能够使用 console.log 语句而不使 ESLint 将其标记为问题。在开发期间需要调试代码时这尤其有用。下面是包含迄今为止我们所做的所有修改的完整配置文件:
import globals from 'globals'
import js from '@eslint/js'
import stylisticJs from '@stylistic/eslint-plugin-js'
export default [
js.configs.recommended,
{
files: ['**/*.js'],
languageOptions: {
sourceType: 'commonjs',
globals: { ...globals.node },
ecmaVersion: 'latest',
},
plugins: {
'@stylistic/js': stylisticJs,
},
rules: {
'@stylistic/js/indent': ['error', 2],
'@stylistic/js/linebreak-style': ['error', 'unix'],
'@stylistic/js/quotes': ['error', 'single'],
'@stylistic/js/semi': ['error', 'never'],
eqeqeq: 'error',
'no-trailing-spaces': 'error',
'object-curly-spacing': ['error', 'always'],
'arrow-spacing': ['error', { before: true, after: true }],
'no-console': 'off',
},
},
{
ignores: ['dist/**'],
},
]注 当你对 eslint.config.mjs 文件进行更改时,推荐从命令行运行 linter。这将验证配置文件的格式是否正确:

如果你的配置文件中有什么错误,lint 插件可能会表现得相当不稳定。
许多公司都会定义编码标准,并通过 ESlint 配置文件在整个组织中强制实施。不推荐反复重新发明轮子,采用别人项目中现成的配置是个明智的选择。最近,许多项目都采用了 Airbnb 的Javascript 样式指南,并使用了 Airbnb 的 ESlint 配置。
你可以在这个 GitHub 仓库的 part3-7 分支中找到我们当前应用程序的全部代码。
