构建一个完整的 Express API 服务_前端框架

🌈个人主页:前端青山 🔥系列专栏:node.js篇
🔖人终将被年少不可得之物困其一生

依旧青山,本期给大家带来node.js篇专栏内容:构建一个完整的 Express API 服务

前言

大家好,我是青山。在前两篇文章中,我们从零开始学习了 Node.js 和 MongoDB 的基础概念,并构建了一个简单的全栈应用。我们还探讨了一些高级功能,如路由管理、中间件、错误处理和安全性。今天,我们将继续深入探讨更多高级功能,包括数据验证、API 文档生成和性能优化。通过这些内容,你将能够构建更加高效和可靠的全栈应用。

目录

前言

一、数据验证

1.1 为什么需要数据验证?

1.2 使用 celebrate 进行数据验证

1.2.1 安装依赖

1.2.2 配置数据验证

1.2.3 代码解释

1.3 自定义错误处理

1.3.1 代码解释

二、API 文档生成

2.1 为什么需要 API 文档?

2.2 使用 swagger 生成 API 文档

2.2.1 安装依赖

2.2.2 配置 Swagger

2.2.3 代码解释

2.2.4 添加 API 注解

2.2.5 代码解释

三、性能优化

3.1 为什么需要性能优化?

3.2 使用 compression 压缩响应

3.2.1 安装依赖

3.2.2 配置 compression

3.2.3 代码解释

3.3 使用 cluster 模块实现多进程

3.3.1 安装依赖

3.3.2 配置 cluster(续)

3.3.3 项目结构

3.3.4 分离路由和模型

routes/items.js

app.js

index.js

3.3.5 运行项目

3.3.6 添加更新和删除物品的路由

routes/items.js

3.3.7 优化错误处理和日志记录

middlewares/error-handler.js

app.js

3.3.8 项目结构

3.3.9 运行项目

3.3.10 添加环境变量管理

安装 dotenv 库

创建 .env 文件

修改 index.js 和 app.js 以使用环境变量

index.js

app.js

3.3.11 优化数据库连接管理

config/db.js

修改 app.js 以使用 connectDB

3.3.12 添加日志记录库

安装 winston 库

创建日志记录中间件

修改 app.js 以使用日志记录中间件

3.3.13 项目结构

3.3.14 运行项目

一、数据验证

1.1 为什么需要数据验证?

数据验证是确保客户端提交的数据符合预期格式和规则的重要步骤。它可以防止无效数据进入数据库,提高应用的稳定性和安全性。数据验证还可以帮助我们在早期发现和处理错误,减少后续的调试和维护成本。

1.2 使用 celebrate 进行数据验证

celebrate 是一个基于 Joi 的 Express 中间件,可以帮助我们轻松地进行数据验证。首先,我们需要安装 celebrate 和 joi

npm install celebrate joi

1.2.1 安装依赖

在项目根目录下运行以下命令:

npm install celebrate joi

1.2.2 配置数据验证

在 index.js 文件中配置 celebrate,对 POST /items 路由进行数据验证:

// index.js
const express = require('express');
const { MongoClient } = require('mongodb');
const helmet = require('helmet');
const { celebrate, Joi } = require('celebrate');

const app = express();
const port = 3000;
const uri = 'mongodb://127.0.0.1:27017';
const client = new MongoClient(uri, { useNewUrlParser: true, useUnifiedTopology: true });

// 配置 Helmet
app.use(helmet());

// 日志中间件
app.use((req, res, next) => {
  console.log(`${new Date().toISOString()} - ${req.method} ${req.url}`);
  next();
});

app.use(express.json()); // 解析 JSON 请求体

// 数据验证中间件
const itemSchema = Joi.object({
  name: Joi.string().required(),
  description: Joi.string().optional(),
  price: Joi.number().min(0).required()
});

// 路由
app.get('/', async (req, res) => {
  try {
    await client.connect();
    console.log('Connected to MongoDB');
    const database = client.db('myFirstDatabase');
    const collection = database.collection('items');

    const query = {};
    const cursor = collection.find(query);

    if ((await cursor.count()) === 0) {
      res.status(200).send('No items found');
    } else {
      const items = await cursor.toArray();
      res.status(200).json(items);
    }
  } catch (err) {
    next(err); // 将错误传递给错误处理中间件
  } finally {
    await client.close();
  }
});

app.post('/items', celebrate({ body: itemSchema }), async (req, res, next) => {
  try {
    await client.connect();
    console.log('Connected to MongoDB');
    const database = client.db('myFirstDatabase');
    const collection = database.collection('items');

    const newItem = req.body;
    const result = await collection.insertOne(newItem);
    res.status(201).json(result.ops[0]);
  } catch (err) {
    next(err); // 将错误传递给错误处理中间件
  } finally {
    await client.close();
  }
});

// 错误处理中间件
app.use((err, req, res, next) => {
  console.error(err.stack);
  res.status(500).send('Internal Server Error');
});

app.listen(port, () => {
  console.log(`Server running at http://127.0.0.1:${port}`);
});

1.2.3 代码解释

  • celebrate({ body: itemSchema }):配置 celebrate 中间件,对请求体进行验证。
  • itemSchema:定义了数据验证的规则,包括 namedescription 和 price 字段的验证规则。
  • Joi.object():创建一个 Joi 对象,用于定义验证规则。
  • Joi.string().required():验证字段是否为字符串且必填。
  • Joi.number().min(0).required():验证字段是否为数字且大于等于 0 且必填。

1.3 自定义错误处理

为了更好地处理数据验证错误,我们可以自定义错误处理中间件,返回更友好的错误信息:

// 自定义错误处理中间件
app.use((err, req, res, next) => {
  if (err.isJoi) {
    return res.status(400).json({ error: err.details[0].message });
  }
  console.error(err.stack);
  res.status(500).send('Internal Server Error');
});

1.3.1 代码解释

  • err.isJoi:检查错误是否来自 celebrate 中间件。
  • err.details[0].message:获取具体的验证错误信息。
  • res.status(400).json({ error: err.details[0].message }):返回 400 状态码和具体的错误信息。

二、API 文档生成

2.1 为什么需要 API 文档?

API 文档是开发过程中不可或缺的一部分,它可以帮助前端开发者和后端开发者更好地理解接口的功能和使用方法。良好的 API 文档可以提高开发效率,减少沟通成本,提高代码质量。

2.2 使用 swagger 生成 API 文档

swagger 是一个流行的工具,可以帮助我们生成和管理 API 文档。首先,我们需要安装 swagger-ui-express 和 swagger-jsdoc

npm install swagger-ui-express swagger-jsdoc

2.2.1 安装依赖

在项目根目录下运行以下命令:

npm install swagger-ui-express swagger-jsdoc

2.2.2 配置 Swagger

在 index.js 文件中配置 swagger,生成 API 文档:

// index.js
const express = require('express');
const { MongoClient } = require('mongodb');
const helmet = require('helmet');
const { celebrate, Joi } = require('celebrate');
const swaggerUi = require('swagger-ui-express');
const swaggerJSDoc = require('swagger-jsdoc');

const app = express();
const port = 3000;
const uri = 'mongodb://127.0.0.1:27017';
const client = new MongoClient(uri, { useNewUrlParser: true, useUnifiedTopology: true });

// 配置 Helmet
app.use(helmet());

// 日志中间件
app.use((req, res, next) => {
  console.log(`${new Date().toISOString()} - ${req.method} ${req.url}`);
  next();
});

app.use(express.json()); // 解析 JSON 请求体

// 数据验证中间件
const itemSchema = Joi.object({
  name: Joi.string().required(),
  description: Joi.string().optional(),
  price: Joi.number().min(0).required()
});

// Swagger 配置
const options = {
  definition: {
    openapi: '3.0.0',
    info: {
      title: 'My API',
      version: '1.0.0',
      description: 'This is a simple API for managing items.',
    },
    servers: [
      {
        url: `http://localhost:${port}`,
      },
    ],
  },
  apis: ['./index.js'], // 指定包含 API 注解的文件
};

const specs = swaggerJSDoc(options);

// 路由
app.get('/', async (req, res) => {
  try {
    await client.connect();
    console.log('Connected to MongoDB');
    const database = client.db('myFirstDatabase');
    const collection = database.collection('items');

    const query = {};
    const cursor = collection.find(query);

    if ((await cursor.count()) === 0) {
      res.status(200).send('No items found');
    } else {
      const items = await cursor.toArray();
      res.status(200).json(items);
    }
  } catch (err) {
    next(err); // 将错误传递给错误处理中间件
  } finally {
    await client.close();
  }
});

app.post('/items', celebrate({ body: itemSchema }), async (req, res, next) => {
  try {
    await client.connect();
    console.log('Connected to MongoDB');
    const database = client.db('myFirstDatabase');
    const collection = database.collection('items');

    const newItem = req.body;
    const result = await collection.insertOne(newItem);
    res.status(201).json(result.ops[0]);
  } catch (err) {
    next(err); // 将错误传递给错误处理中间件
  } finally {
    await client.close();
  }
});

// Swagger UI
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(specs));

// 自定义错误处理中间件
app.use((err, req, res, next) => {
  if (err.isJoi) {
    return res.status(400).json({ error: err.details[0].message });
  }
  console.error(err.stack);
  res.status(500).send('Internal Server Error');
});

app.listen(port, () => {
  console.log(`Server running at http://127.0.0.1:${port}`);
});

2.2.3 代码解释

  • swaggerJSDoc(options):生成 Swagger 文档。
  • swaggerUi.serve:提供 Swagger UI 的静态文件。
  • swaggerUi.setup(specs):设置 Swagger UI,显示生成的文档。
  • options:Swagger 配置对象,包括 API 的基本信息和包含 API 注解的文件路径。

2.2.4 添加 API 注解

在 index.js 文件中添加 API 注解,以便 swagger-jsdoc 可以生成文档:

/**
 * @swagger
 * /:
 *   get:
 *     summary: 获取所有项
 *     responses:
 *       200:
 *         description: 成功返回所有项
 *         content:
 *           application/json:
 *             schema:
 *               type: array
 *               items:
 *                 $ref: '#/components/schemas/Item'
 *       404:
 *         description: 未找到项
 *       500:
 *         description: 内部服务器错误
 */

/**
 * @swagger
 * /items:
 *   post:
 *     summary: 插入新项
 *     requestBody:
 *       required: true
 *       content:
 *         application/json:
 *           schema:
 *             $ref: '#/components/schemas/Item'
 *     responses:
 *       201:
 *         description: 新项成功插入
 *         content:
 *           application/json:
 *             schema:
 *               $ref: '#/components/schemas/Item'
 *       400:
 *         description: 请求体验证失败
 *       500:
 *         description: 内部服务器错误
 */

/**
 * @swagger
 * components:
 *   schemas:
 *     Item:
 *       type: object
 *       properties:
 *         name:
 *           type: string
 *           description: 项的名称
 *         description:
 *           type: string
 *           description: 项的描述
 *         price:
 *           type: number
 *           description: 项的价格
 */

2.2.5 代码解释

  • @swagger:标记 Swagger 注解的开始。
  • summary:API 的简短描述。
  • responses:API 的响应列表,包括状态码和描述。
  • requestBody:API 的请求体,包括验证规则。
  • components:定义 API 的组件,如数据模型。

三、性能优化

3.1 为什么需要性能优化?

性能优化是提高应用响应速度和用户体验的关键步骤。通过优化代码和配置,我们可以减少请求的响应时间,提高应用的吞吐量,降低服务器资源的消耗。

3.2 使用 compression 压缩响应

compression 是一个 Express 中间件,可以帮助我们压缩响应体,减少传输数据的大小,提高网络传输效率。首先,我们需要安装 compression

npm install compression

3.2.1 安装依赖

在项目根目录下运行以下命令:

npm install compression

3.2.2 配置 compression

在 index.js 文件中配置 compression,压缩响应体:

// index.js
const express = require('express');
const { MongoClient } = require('mongodb');
const helmet = require('helmet');
const { celebrate, Joi } = require('celebrate');
const swaggerUi = require('swagger-ui-express');
const swaggerJSDoc = require('swagger-jsdoc');
const compression = require('compression');

const app = express();
const port = 3000;
const uri = 'mongodb://127.0.0.1:27017';
const client = new MongoClient(uri, { useNewUrlParser: true, useUnifiedTopology: true });

// 配置 Helmet
app.use(helmet());

// 日志中间件
app.use((req, res, next) => {
  console.log(`${new Date().toISOString()} - ${req.method} ${req.url}`);
  next();
});

app.use(express.json()); // 解析 JSON 请求体

// 数据验证中间件
const itemSchema = Joi.object({
  name: Joi.string().required(),
  description: Joi.string().optional(),
  price: Joi.number().min(0).required()
});

// Swagger 配置
const options = {
  definition: {
    openapi: '3.0.0',
    info: {
      title: 'My API',
      version: '1.0.0',
      description: 'This is a simple API for managing items.',
    },
    servers: [
      {
        url: `http://localhost:${port}`,
      },
    ],
  },
  apis: ['./index.js'], // 指定包含 API 注解的文件
};

const specs = swaggerJSDoc(options);

// 压缩响应体
app.use(compression());

// 路由
app.get('/', async (req, res) => {
  try {
    await client.connect();
    console.log('Connected to MongoDB');
    const database = client.db('myFirstDatabase');
    const collection = database.collection('items');

    const query = {};
    const cursor = collection.find(query);

    if ((await cursor.count()) === 0) {
      res.status(200).send('No items found');
    } else {
      const items = await cursor.toArray();
      res.status(200).json(items);
    }
  } catch (err) {
    next(err); // 将错误传递给错误处理中间件
  } finally {
    await client.close();
  }
});

app.post('/items', celebrate({ body: itemSchema }), async (req, res, next) => {
  try {
    await client.connect();
    console.log('Connected to MongoDB');
    const database = client.db('myFirstDatabase');
    const collection = database.collection('items');

    const newItem = req.body;
    const result = await collection.insertOne(newItem);
    res.status(201).json(result.ops[0]);
  } catch (err) {
    next(err); // 将错误传递给错误处理中间件
  } finally {
    await client.close();
  }
});

// Swagger UI
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(specs));

// 自定义错误处理中间件
app.use((err, req, res, next) => {
  if (err.isJoi) {
    return res.status(400).json({ error: err.details[0].message });
  }
  console.error(err.stack);
  res.status(500).send('Internal Server Error');
});

app.listen(port, () => {
  console.log(`Server running at http://127.0.0.1:${port}`);
});

3.2.3 代码解释

  • app.use(compression()):配置 compression 中间件,压缩响应体。

3.3 使用 cluster 模块实现多进程

cluster 模块可以帮助我们利用多核 CPU 的优势,提高应用的并发处理能力。通过创建多个工作进程,我们可以显著提高应用的性能。首先,我们需要安装 os 模块:

npm install os

3.3.1 安装依赖

在项目根目录下运行以下命令:

npm install os

3.3.2 配置 cluster(续)

app.post('/items', celebrate({ body: itemSchema }), async (req, res, next) => {
    try {
      await client.connect();
      console.log('Connected to MongoDB');
      const database = client.db('myFirstDatabase');
      const collection = database.collection('items');

      const newItem = req.body;
      const result = await collection.insertOne(newItem);

      res.status(201).json(result.ops[0]);
    } catch (err) {
      next(err); // 将错误传递给错误处理中间件
    } finally {
      await client.close();
    }
  });

  // Swagger UI
  app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(specs));

  // 错误处理中间件
  app.use((err, req, res, next) => {
    console.error(err.stack);
    res.status(500).send('Something broke!');
  });

  // 启动服务器
  app.listen(port, () => {
    console.log(`Worker ${process.pid} listening on port ${port}`);
  });
}

3.3.3 项目结构

为了更好地组织代码,建议将项目结构如下:

my-app/
├── node_modules/
├── public/
│   └── index.html
├── routes/
│   └── items.js
├── models/
│   └── item.js
├── middlewares/
│   └── error-handler.js
├── config/
│   └── db.js
├── app.js
└── index.js

3.3.4 分离路由和模型

routes/items.js
const express = require('express');
const { celebrate, Joi } = require('celebrate');
const { MongoClient } = require('mongodb');
const uri = 'mongodb://127.0.0.1:27017';

const router = express.Router();

const itemSchema = Joi.object({
  name: Joi.string().required(),
  description: Joi.string().optional(),
  price: Joi.number().min(0).required()
});

router.get('/', async (req, res, next) => {
  try {
    const client = new MongoClient(uri, { useNewUrlParser: true, useUnifiedTopology: true });
    await client.connect();
    console.log('Connected to MongoDB');
    const database = client.db('myFirstDatabase');
    const collection = database.collection('items');

    const query = {};
    const cursor = collection.find(query);

    if ((await cursor.count()) === 0) {
      res.status(200).send('No items found');
    } else {
      const items = await cursor.toArray();
      res.status(200).json(items);
    }
  } catch (err) {
    next(err); // 将错误传递给错误处理中间件
  } finally {
    await client.close();
  }
});

router.post('/', celebrate({ body: itemSchema }), async (req, res, next) => {
  try {
    const client = new MongoClient(uri, { useNewUrlParser: true, useUnifiedTopology: true });
    await client.connect();
    console.log('Connected to MongoDB');
    const database = client.db('myFirstDatabase');
    const collection = database.collection('items');

    const newItem = req.body;
    const result = await collection.insertOne(newItem);

    res.status(201).json(result.ops[0]);
  } catch (err) {
    next(err); // 将错误传递给错误处理中间件
  } finally {
    await client.close();
  }
});

module.exports = router;
app.js
const express = require('express');
const helmet = require('helmet');
const compression = require('compression');
const swaggerUi = require('swagger-ui-express');
const swaggerJSDoc = require('swagger-jsdoc');
const itemsRouter = require('./routes/items');

const app = express();

// 配置 Helmet
app.use(helmet());

// 日志中间件
app.use((req, res, next) => {
  console.log(`${new Date().toISOString()} - ${req.method} ${req.url}`);
  next();
});

app.use(express.json()); // 解析 JSON 请求体

// 压缩响应体
app.use(compression());

// 路由
app.use('/items', itemsRouter);

// Swagger 配置
const options = {
  definition: {
    openapi: '3.0.0',
    info: {
      title: 'My API',
      version: '1.0.0',
      description: 'This is a simple API for managing items.',
    },
    servers: [
      {
        url: `http://localhost:3000`,
      },
    ],
  },
  apis: ['./routes/*.js'], // 指定包含 API 注解的文件
};

const specs = swaggerJSDoc(options);
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(specs));

// 错误处理中间件
app.use((err, req, res, next) => {
  console.error(err.stack);
  res.status(500).send('Something broke!');
});

module.exports = app;
index.js
const cluster = require('cluster');
const os = require('os');
const app = require('./app');
const port = 3000;

const numCPUs = os.cpus().length;

if (cluster.isMaster) {
  console.log(`Master ${process.pid} is running`);

  // Fork workers.
  for (let i = 0; i < numCPUs; i++) {
    cluster.fork();
  }

  cluster.on('exit', (worker, code, signal) => {
    console.log(`Worker ${worker.process.pid} died`);
  });
} else {
  app.listen(port, () => {
    console.log(`Worker ${process.pid} listening on port ${port}`);
  });
}

3.3.5 运行项目

  1. 确保 MongoDB 服务已启动。
  2. 在项目根目录下运行以下命令启动应用:
npm install node index.js

现在,你的应用已经配置了多进程支持,并且路由和模型已经分离,更加模块化和可维护。你可以通过访问 http://localhost:3000/api-docs 查看 Swagger 文档。

3.3.6 添加更新和删除物品的路由

routes/items.js

在 routes/items.js 中添加 PUT 和 DELETE 路由:

const express = require('express');
const { celebrate, Joi } = require('celebrate');
const { MongoClient } = require('mongodb');
const uri = 'mongodb://127.0.0.1:27017';

const router = express.Router();

const itemSchema = Joi.object({
  name: Joi.string().required(),
  description: Joi.string().optional(),
  price: Joi.number().min(0).required()
});

const updateItemSchema = Joi.object({
  name: Joi.string().optional(),
  description: Joi.string().optional(),
  price: Joi.number().min(0).optional()
});

router.get('/', async (req, res, next) => {
  try {
    const client = new MongoClient(uri, { useNewUrlParser: true, useUnifiedTopology: true });
    await client.connect();
    console.log('Connected to MongoDB');
    const database = client.db('myFirstDatabase');
    const collection = database.collection('items');

    const query = {};
    const cursor = collection.find(query);

    if ((await cursor.count()) === 0) {
      res.status(200).send('No items found');
    } else {
      const items = await cursor.toArray();
      res.status(200).json(items);
    }
  } catch (err) {
    next(err); // 将错误传递给错误处理中间件
  } finally {
    await client.close();
  }
});

router.post('/', celebrate({ body: itemSchema }), async (req, res, next) => {
  try {
    const client = new MongoClient(uri, { useNewUrlParser: true, useUnifiedTopology: true });
    await client.connect();
    console.log('Connected to MongoDB');
    const database = client.db('myFirstDatabase');
    const collection = database.collection('items');

    const newItem = req.body;
    const result = await collection.insertOne(newItem);

    res.status(201).json(result.ops[0]);
  } catch (err) {
    next(err); // 将错误传递给错误处理中间件
  } finally {
    await client.close();
  }
});

router.put('/:id', celebrate({ body: updateItemSchema, params: Joi.object({ id: Joi.string().required() }) }), async (req, res, next) => {
  try {
    const client = new MongoClient(uri, { useNewUrlParser: true, useUnifiedTopology: true });
    await client.connect();
    console.log('Connected to MongoDB');
    const database = client.db('myFirstDatabase');
    const collection = database.collection('items');

    const filter = { _id: new ObjectId(req.params.id) };
    const update = { $set: req.body };
    const result = await collection.updateOne(filter, update);

    if (result.matchedCount === 0) {
      res.status(404).send('Item not found');
    } else {
      res.status(200).json({ message: 'Item updated successfully' });
    }
  } catch (err) {
    next(err); // 将错误传递给错误处理中间件
  } finally {
    await client.close();
  }
});

router.delete('/:id', celebrate({ params: Joi.object({ id: Joi.string().required() }) }), async (req, res, next) => {
  try {
    const client = new MongoClient(uri, { useNewUrlParser: true, useUnifiedTopology: true });
    await client.connect();
    console.log('Connected to MongoDB');
    const database = client.db('myFirstDatabase');
    const collection = database.collection('items');

    const filter = { _id: new ObjectId(req.params.id) };
    const result = await collection.deleteOne(filter);

    if (result.deletedCount === 0) {
      res.status(404).send('Item not found');
    } else {
      res.status(200).json({ message: 'Item deleted successfully' });
    }
  } catch (err) {
    next(err); // 将错误传递给错误处理中间件
  } finally {
    await client.close();
  }
});

module.exports = router;

3.3.7 优化错误处理和日志记录

middlewares/error-handler.js

创建一个错误处理中间件来统一处理所有错误:

const errorHandler = (err, req, res, next) => {
  console.error(err.stack);
  res.status(500).json({ message: 'Internal Server Error' });
};

module.exports = errorHandler;
app.js

在 app.js 中引入并使用错误处理中间件:

const express = require('express');
const helmet = require('helmet');
const compression = require('compression');
const swaggerUi = require('swagger-ui-express');
const swaggerJSDoc = require('swagger-jsdoc');
const itemsRouter = require('./routes/items');
const errorHandler = require('./middlewares/error-handler');

const app = express();

// 配置 Helmet
app.use(helmet());

// 日志中间件
app.use((req, res, next) => {
  console.log(`${new Date().toISOString()} - ${req.method} ${req.url}`);
  next();
});

app.use(express.json()); // 解析 JSON 请求体

// 压缩响应体
app.use(compression());

// 路由
app.use('/items', itemsRouter);

// Swagger 配置
const options = {
  definition: {
    openapi: '3.0.0',
    info: {
      title: 'My API',
      version: '1.0.0',
      description: 'This is a simple API for managing items.',
    },
    servers: [
      {
        url: `http://localhost:3000`,
      },
    ],
  },
  apis: ['./routes/*.js'], // 指定包含 API 注解的文件
};

const specs = swaggerJSDoc(options);
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(specs));

// 错误处理中间件
app.use(errorHandler);

module.exports = app;

3.3.8 项目结构

确保项目结构如下:

my-app/
├── node_modules/
├── public/
│   └── index.html
├── routes/
│   └── items.js
├── models/
│   └── item.js
├── middlewares/
│   └── error-handler.js
├── config/
│   └── db.js
├── app.js
└── index.js

3.3.9 运行项目

  1. 确保 MongoDB 服务已启动。
  2. 在项目根目录下运行以下命令启动应用:
npm install node index.js

现在,你的应用已经包含了完整的 CRUD 操作,并且具有更好的错误处理和日志记录功能。你可以通过访问 http://localhost:3000/api-docs 查看 Swagger 文档,并测试各个 API 端点。

3.3.10 添加环境变量管理

安装 dotenv 库

首先,安装 dotenv 库来管理环境变量:

npm install dotenv
创建 .env 文件

在项目根目录下创建一个 .env 文件,并添加以下内容:

PORT=3000 MONGODB_URI=mongodb://127.0.0.1:27017/myFirstDatabase
修改 index.js 和 app.js 以使用环境变量
index.js
require('dotenv').config();
const cluster = require('cluster');
const os = require('os');
const app = require('./app');
const port = process.env.PORT || 3000;

const numCPUs = os.cpus().length;

if (cluster.isMaster) {
  console.log(`Master ${process.pid} is running`);

  // Fork workers.
  for (let i = 0; i < numCPUs; i++) {
    cluster.fork();
  }

  cluster.on('exit', (worker, code, signal) => {
    console.log(`Worker ${worker.process.pid} died`);
  });
} else {
  app.listen(port, () => {
    console.log(`Worker ${process.pid} listening on port ${port}`);
  });
}
app.js
require('dotenv').config();
const express = require('express');
const helmet = require('helmet');
const compression = require('compression');
const swaggerUi = require('swagger-ui-express');
const swaggerJSDoc = require('swagger-jsdoc');
const itemsRouter = require('./routes/items');
const errorHandler = require('./middlewares/error-handler');
const mongoose = require('mongoose');

const app = express();

// 配置 Helmet
app.use(helmet());

// 日志中间件
app.use((req, res, next) => {
  console.log(`${new Date().toISOString()} - ${req.method} ${req.url}`);
  next();
});

app.use(express.json()); // 解析 JSON 请求体

// 压缩响应体
app.use(compression());

// 连接 MongoDB
mongoose.connect(process.env.MONGODB_URI, { useNewUrlParser: true, useUnifiedTopology: true })
  .then(() => console.log('Connected to MongoDB'))
  .catch(err => console.error('Failed to connect to MongoDB', err));

// 路由
app.use('/items', itemsRouter);

// Swagger 配置
const options = {
  definition: {
    openapi: '3.0.0',
    info: {
      title: 'My API',
      version: '1.0.0',
      description: 'This is a simple API for managing items.',
    },
    servers: [
      {
        url: `http://localhost:${process.env.PORT || 3000}`,
      },
    ],
  },
  apis: ['./routes/*.js'], // 指定包含 API 注解的文件
};

const specs = swaggerJSDoc(options);
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(specs));

// 错误处理中间件
app.use(errorHandler);

module.exports = app;

3.3.11 优化数据库连接管理

为了优化数据库连接管理,我们可以在 config/db.js 中创建一个单独的模块来管理数据库连接。

config/db.js
const mongoose = require('mongoose');

const connectDB = async () => {
  try {
    await mongoose.connect(process.env.MONGODB_URI, { useNewUrlParser: true, useUnifiedTopology: true });
    console.log('Connected to MongoDB');
  } catch (err) {
    console.error('Failed to connect to MongoDB', err);
    process.exit(1); // 退出进程
  }
};

module.exports = connectDB;
修改 app.js 以使用 connectDB
require('dotenv').config();
const express = require('express');
const helmet = require('helmet');
const compression = require('compression');
const swaggerUi = require('swagger-ui-express');
const swaggerJSDoc = require('swagger-jsdoc');
const itemsRouter = require('./routes/items');
const errorHandler = require('./middlewares/error-handler');
const connectDB = require('./config/db');

const app = express();

// 配置 Helmet
app.use(helmet());

// 日志中间件
app.use((req, res, next) => {
  console.log(`${new Date().toISOString()} - ${req.method} ${req.url}`);
  next();
});

app.use(express.json()); // 解析 JSON 请求体

// 压缩响应体
app.use(compression());

// 连接 MongoDB
connectDB();

// 路由
app.use('/items', itemsRouter);

// Swagger 配置
const options = {
  definition: {
    openapi: '3.0.0',
    info: {
      title: 'My API',
      version: '1.0.0',
      description: 'This is a simple API for managing items.',
    },
    servers: [
      {
        url: `http://localhost:${process.env.PORT || 3000}`,
      },
    ],
  },
  apis: ['./routes/*.js'], // 指定包含 API 注解的文件
};

const specs = swaggerJSDoc(options);
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(specs));

// 错误处理中间件
app.use(errorHandler);

module.exports = app;

3.3.12 添加日志记录库

安装 winston 库

安装 winston 库来管理日志记录:

npm install winston
创建日志记录中间件

在 middlewares 目录下创建 logger.js 文件:

const winston = require('winston');

const logger = winston.createLogger({
  level: 'info',
  format: winston.format.combine(
    winston.format.timestamp(),
    winston.format.json()
  ),
  transports: [
    new winston.transports.Console(),
    new winston.transports.File({ filename: 'logs/error.log', level: 'error' }),
    new winston.transports.File({ filename: 'logs/combined.log' })
  ]
});

module.exports = logger;
修改 app.js 以使用日志记录中间件
require('dotenv').config();
const express = require('express');
const helmet = require('helmet');
const compression = require('compression');
const swaggerUi = require('swagger-ui-express');
const swaggerJSDoc = require('swagger-jsdoc');
const itemsRouter = require('./routes/items');
const errorHandler = require('./middlewares/error-handler');
const connectDB = require('./config/db');
const logger = require('./middlewares/logger');

const app = express();

// 配置 Helmet
app.use(helmet());

// 日志中间件
app.use((req, res, next) => {
  logger.info(`${req.method} ${req.url}`);
  next();
});

app.use(express.json()); // 解析 JSON 请求体

// 压缩响应体
app.use(compression());

// 连接 MongoDB
connectDB();

// 路由
app.use('/items', itemsRouter);

// Swagger 配置
const options = {
  definition: {
    openapi: '3.0.0',
    info: {
      title: 'My API',
      version: '1.0.0',
      description: 'This is a simple API for managing items.',
    },
    servers: [
      {
        url: `http://localhost:${process.env.PORT || 3000}`,
      },
    ],
  },
  apis: ['./routes/*.js'], // 指定包含 API 注解的文件
};

const specs = swaggerJSDoc(options);
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(specs));

// 错误处理中间件
app.use(errorHandler);

module.exports = app;

3.3.13 项目结构

确保项目结构如下:

my-app/
├── node_modules/
├── public/
│   └── index.html
├── routes/
│   └── items.js
├── models/
│   └── item.js
├── middlewares/
│   ├── error-handler.js
│   └── logger.js
├── config/
│   └── db.js
├── .env
├── app.js
└── index.js

3.3.14 运行项目

  1. 确保 MongoDB 服务已启动。
  2. 在项目根目录下运行以下命令启动应用:
npm install node index.js

现在,你的应用已经包含了环境变量管理、日志记录和优化的数据库连接管理。你可以通过访问 http://localhost:3000/api-docs 查看 Swagger 文档,并测试各个 API 端点。