跳到内容

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 字段添加任何约束,所以它在模式中的定义没有改变。

minLengthrequired 验证器是 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 的默认错误消息:

postman显示错误消息

将数据库后端部署到生产环境

该应用程序应该能在 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 打开日志时,问题就显而易见了:

fly.io服务器日志显示连接到未定义

数据库 URL 是 undefined ,所以是忘记执行 fly secrets set MONGODB_URI 命令了。

你还需要在 MongoDB Atlas 中将 fly.io 应用的 IP 地址添加到白名单中。否则 MongoDB 会拒绝连接。

遗憾的是,fly.io 不会给你的应用提供一个专门的 IPv4 地址,所以你需要在 MongoDB Atlas 中允许所有的 IP 地址。

在使用 Render 时,可以通过在仪表板中定义适当的环境变量来提供数据库 URL:

render仪表板显示MONGODB_URI环境变量

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

render仪表板上有箭头指向在端口10000上运行的服务器

你可以在这个 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-dev

package.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初始化的终端输出

配置将会保存在 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,来允许浏览器特有的全局变量,比如 windowdocument

最后,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 对我们的代码有很多意见:

ESlint错误的终端输出

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

VS Code ESlint 插件会用红线划出违反样式的地方:

VScode 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。这将验证配置文件的格式是否正确:

npm run lint 在终端的输出

如果你的配置文件中有什么错误,lint 插件可能会表现得相当不稳定。

许多公司都会定义编码标准,并通过 ESlint 配置文件在整个组织中强制实施。不推荐反复重新发明轮子,采用别人项目中现成的配置是个明智的选择。最近,许多项目都采用了 Airbnb 的Javascript 样式指南,并使用了 Airbnb 的 ESlint 配置。

你可以在这个 GitHub 仓库part3-7 分支中找到我们当前应用程序的全部代码。