Node.js-增强 API 安全性和性能优化

​🌈个人主页:前端青山

🔥系列专栏:node.js篇

🔖人终将被年少不可得之物困其一生

依旧青山,本期给大家带来node.js篇专栏内容:node.js-增强 API 安全性和性能优化

前言

在前几篇文章中,我们已经构建了一个基本的 Express API 服务,实现了 CRUD 操作、环境变量管理、日志记录、错误处理和数据库连接优化。本文将继续在这个基础上,进一步增强 API 的安全性和性能优化。我们将添加身份验证、CORS 配置、缓存机制和更详细的性能监控。

目录

前言

[1. 添加身份验证](#1. 添加身份验证)

[1.1 安装依赖](#1.1 安装依赖)

[1.2 创建用户模型](#1.2 创建用户模型)

[1.3 创建用户注册和登录路由](#1.3 创建用户注册和登录路由)

[1.4 添加身份验证中间件](#1.4 添加身份验证中间件)

[1.5 保护受限制的路由](#1.5 保护受限制的路由)

[2. 配置 CORS](#2. 配置 CORS)

[3. 添加缓存机制](#3. 添加缓存机制)

[3.1 创建 Redis 客户端](#3.1 创建 Redis 客户端)

[3.2 添加缓存中间件](#3.2 添加缓存中间件)

[3.3 使用缓存中间件](#3.3 使用缓存中间件)

[4. 性能监控](#4. 性能监控)

项目结构

运行项目


1. 添加身份验证

为了保护 API 的安全性,我们将添加 JWT(JSON Web Token)身份验证。JWT 是一种广泛使用的身份验证机制,可以确保只有经过身份验证的用户才能访问受保护的资源。

1.1 安装依赖

安装 jsonwebtokenbcryptjs 库:

javascript 复制代码
npm install jsonwebtoken bcryptjs
1.2 创建用户模型

models 目录下创建 user.js 文件:

javascript 复制代码
const mongoose = require('mongoose');

const userSchema = new mongoose.Schema({
  username: { type: String, required: true, unique: true },
  password: { type: String, required: true }
});

const User = mongoose.model('User', userSchema);

module.exports = User;
1.3 创建用户注册和登录路由

routes 目录下创建 auth.js 文件:

javascript 复制代码
const express = require('express');
const { celebrate, Joi } = require('celebrate');
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');
const User = require('../models/user');
const { SECRET_KEY } = process.env;

const router = express.Router();

const registerSchema = Joi.object({
  username: Joi.string().required(),
  password: Joi.string().required()
});

const loginSchema = Joi.object({
  username: Joi.string().required(),
  password: Joi.string().required()
});

router.post('/register', celebrate({ body: registerSchema }), async (req, res, next) => {
  try {
    const { username, password } = req.body;
    const hashedPassword = await bcrypt.hash(password, 10);
    const newUser = new User({ username, password: hashedPassword });
    await newUser.save();
    res.status(201).json({ message: 'User registered successfully' });
  } catch (err) {
    next(err);
  }
});

router.post('/login', celebrate({ body: loginSchema }), async (req, res, next) => {
  try {
    const { username, password } = req.body;
    const user = await User.findOne({ username });

    if (!user || !(await bcrypt.compare(password, user.password))) {
      return res.status(401).json({ message: 'Invalid credentials' });
    }

    const token = jwt.sign({ userId: user._id }, SECRET_KEY, { expiresIn: '1h' });
    res.status(200).json({ token });
  } catch (err) {
    next(err);
  }
});

module.exports = router;
1.4 添加身份验证中间件

middlewares 目录下创建 auth.js 文件:

javascript 复制代码
const jwt = require('jsonwebtoken');
const { SECRET_KEY } = process.env;

const authenticate = (req, res, next) => {
  const token = req.header('Authorization')?.replace('Bearer ', '');

  if (!token) {
    return res.status(401).json({ message: 'Access denied. No token provided.' });
  }

  try {
    const decoded = jwt.verify(token, SECRET_KEY);
    req.user = decoded;
    next();
  } catch (err) {
    res.status(400).json({ message: 'Invalid token' });
  }
};

module.exports = authenticate;
1.5 保护受限制的路由

修改 routes/items.js 文件,添加身份验证中间件:

javascript 复制代码
const express = require('express');
const { celebrate, Joi } = require('celebrate');
const { MongoClient } = require('mongodb');
const ObjectId = require('mongodb').ObjectId;
const authenticate = require('../middlewares/auth');
const cacheMiddleware = require('../middlewares/cache');

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('/', authenticate, cacheMiddleware(60), async (req, res, next) => {
  try {
    const client = new MongoClient(process.env.MONGODB_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('/', authenticate, celebrate({ body: itemSchema }), async (req, res, next) => {
  try {
    const client = new MongoClient(process.env.MONGODB_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', authenticate, celebrate({ body: updateItemSchema, params: Joi.object({ id: Joi.string().required() }) }), async (req, res, next) => {
  try {
    const client = new MongoClient(process.env.MONGODB_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', authenticate, celebrate({ params: Joi.object({ id: Joi.string().required() }) }), async (req, res, next) => {
  try {
    const client = new MongoClient(process.env.MONGODB_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;
2. 配置 CORS

为了允许跨域请求,我们需要配置 CORS。安装 cors 库:

bash 复制代码
npm install cors

修改 app.js 文件,添加 CORS 配置:

javascript 复制代码
require('dotenv').config();
const express = require('express');
const helmet = require('helmet');
const compression = require('compression');
const cors = require('cors');
const swaggerUi = require('swagger-ui-express');
const swaggerJSDoc = require('swagger-jsdoc');
const itemsRouter = require('./routes/items');
const authRouter = require('./routes/auth');
const errorHandler = require('./middlewares/error-handler');
const connectDB = require('./config/db');
const logger = require('./middlewares/logger');
const statusMonitor = require('express-status-monitor');

const app = express();

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

// 配置 CORS
app.use(cors());

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

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

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

// 连接 MongoDB
connectDB();

// 性能监控
app.use(statusMonitor());

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

// 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. 添加缓存机制

为了提高性能,我们可以使用 redis 作为缓存层。安装 redisconnect-redis 库:

bash 复制代码
npm install redis connect-redis
3.1 创建 Redis 客户端

config 目录下创建 redis.js 文件:

javascript 复制代码
const redis = require('redis');
const { REDIS_URL } = process.env;

const client = redis.createClient(REDIS_URL);

client.on('error', (err) => {
  console.error('Redis Client Error', err);
});

client.connect();

module.exports = client;
3.2 添加缓存中间件

middlewares 目录下创建 cache.js 文件:

javascript 复制代码
const redis = require('../config/redis');

const cacheMiddleware = (duration) => {
  return async (req, res, next) => {
    const key = req.originalUrl;
    const cachedData = await redis.get(key);

    if (cachedData) {
      res.send(JSON.parse(cachedData));
      return;
    }

    res.sendResponse = res.send;
    res.send = (body) => {
      redis.setEx(key, duration, JSON.stringify(body));
      res.sendResponse(body);
    };

    next();
  };
};

module.exports = cacheMiddleware;
3.3 使用缓存中间件

修改 routes/items.js 文件,添加缓存中间件:

javascript 复制代码
const express = require('express');
const { celebrate, Joi } = require('celebrate');
const { MongoClient } = require('mongodb');
const ObjectId = require('mongodb').ObjectId;
const authenticate = require('../middlewares/auth');
const cacheMiddleware = require('../middlewares/cache');

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('/', authenticate, cacheMiddleware(60), async (req, res, next) => {
  try {
    const client = new MongoClient(process.env.MONGODB_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('/', authenticate, celebrate({ body: itemSchema }), async (req, res, next) => {
  try {
    const client = new MongoClient(process.env.MONGODB_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', authenticate, celebrate({ body: updateItemSchema, params: Joi.object({ id: Joi.string().required() }) }), async (req, res, next) => {
  try {
    const client = new MongoClient(process.env.MONGODB_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', authenticate, celebrate({ params: Joi.object({ id: Joi.string().required() }) }), async (req, res, next) => {
  try {
    const client = new MongoClient(process.env.MONGODB_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;
4. 性能监控

为了更好地监控应用的性能,我们可以使用 express-status-monitor 库。安装 express-status-monitor

javascript 复制代码
npm install express-status-monitor

修改 app.js 文件,添加性能监控中间件:

javascript 复制代码
require('dotenv').config();
const express = require('express');
const helmet = require('helmet');
const compression = require('compression');
const cors = require('cors');
const swaggerUi = require('swagger-ui-express');
const swaggerJSDoc = require('swagger-jsdoc');
const itemsRouter = require('./routes/items');
const authRouter = require('./routes/auth');
const errorHandler = require('./middlewares/error-handler');
const connectDB = require('./config/db');
const logger = require('./middlewares/logger');
const statusMonitor = require('express-status-monitor');

const app = express();

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

// 配置 CORS
app.use(cors());

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

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

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

// 连接 MongoDB
connectDB();

// 性能监控
app.use(statusMonitor());

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

// 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;
项目结构

确保项目结构如下:

javascript 复制代码
my-app/
├── node_modules/
├── public/
│   └── index.html
├── routes/
│   ├── items.js
│   └── auth.js
├── models/
│   ├── item.js
│   └── user.js
├── middlewares/
│   ├── error-handler.js
│   ├── logger.js
│   ├── auth.js
│   └── cache.js
├── config/
│   ├── db.js
│   └── redis.js
├── .env
├── app.js
└── index.js

运行项目

确保 MongoDB 和 Redis 服务已启动。在项目根目录下运行以下命令启动应用:

bash 复制代码
npm install node index.js

访问 http://localhost:3000/api-docs 查看 Swagger 文档,访问 http://localhost:3000/status 查看性能监控页面。

相关推荐
wgod2 分钟前
new AbortController()
前端
UXbot8 分钟前
UXbot 是什么?一句指令生成完整应用的 AI 工具
前端·ai·交互·个人开发·ai编程·原型模式·ux
棒棒的唐23 分钟前
WSL2用npm安装的openclaw,无法正常使用openclaw gateway start启动服务的问题
前端·npm·gateway
吴声子夜歌31 分钟前
Node.js——操作MySQL数据库
数据库·mysql·node.js
哔哩哔哩技术31 分钟前
使用Compose Navigation3进行屏幕适配
前端
乱蜂朝王31 分钟前
使用 C# 和 ONNX Runtime 部署 PaDiM 异常检测模型
开发语言·c#
波诺波35 分钟前
p1项目system_model.py代码
开发语言·python
危笑ioi36 分钟前
helm部署skywalking链路追踪 java
java·开发语言·skywalking
静心观复44 分钟前
Python 虚拟环境与 pipx 详解
开发语言·python
卷心菜狗1 小时前
Re.从零开始使用Python构建本地大模型网页智慧聊天机器人
开发语言·python·机器人