Swagger 在 Node.js 项目中的安装和完整使用方法

下面详细介绍 Swagger 在 Node.js 项目中的安装和完整使用方法,生成可视化的 API 文档界面。


📦 第一步:安装 Swagger 依赖

bash 复制代码
npm install swagger-jsdoc swagger-ui-express
  • swagger-jsdoc:将 JSDoc 注释转换为 Swagger/OpenAPI 规范
  • swagger-ui-express:提供 Swagger UI 界面

⚙️ 第二步:配置 Swagger

创建 src/config/swagger.js

javascript 复制代码
// src/config/swagger.js
const swaggerJsdoc = require('swagger-jsdoc')

const options = {
  definition: {
    openapi: '3.0.0',
    info: {
      title: '用户管理 API 文档',
      version: '1.0.0',
      description: '基于 Express + JWT 的用户管理接口文档',
      contact: {
        name: 'API Support',
        email: 'support@example.com',
      },
      license: {
        name: 'MIT',
        url: 'https://opensource.org/licenses/MIT',
      },
    },
    servers: [
      {
        url: 'http://localhost:3000/api',
        description: '开发服务器',
      },
      {
        url: 'https://api.example.com/api',
        description: '生产服务器',
      },
    ],
    components: {
      securitySchemes: {
        bearerAuth: {
          type: 'http',
          scheme: 'bearer',
          bearerFormat: 'JWT',
          description: '输入 Bearer Token',
        },
      },
      schemas: {
        // 通用响应格式
        ApiResponse: {
          type: 'object',
          properties: {
            code: {
              type: 'integer',
              description: '状态码:0-成功,其他-失败',
              example: 0,
            },
            msg: {
              type: 'string',
              description: '响应消息',
              example: '操作成功',
            },
            data: {
              type: 'object',
              description: '响应数据',
            },
          },
        },
        // 登录请求
        LoginRequest: {
          type: 'object',
          required: ['username', 'password'],
          properties: {
            username: {
              type: 'string',
              description: '用户名',
              example: 'admin',
            },
            password: {
              type: 'string',
              description: '密码',
              example: '123456',
            },
          },
        },
        // 登录响应
        LoginResponse: {
          type: 'object',
          properties: {
            code: { type: 'integer', example: 0 },
            msg: { type: 'string', example: '登录成功' },
            data: {
              type: 'object',
              properties: {
                token: {
                  type: 'string',
                  description: 'JWT Token',
                  example: 'eyJhbGciOiJIUzI1NiIs...',
                },
                userInfo: {
                  type: 'object',
                  properties: {
                    id: { type: 'integer', example: 1 },
                    username: { type: 'string', example: 'admin' },
                    email: { type: 'string', example: 'admin@example.com' },
                    role: { type: 'string', example: 'admin' },
                    status: { type: 'integer', example: 1 },
                  },
                },
              },
            },
          },
        },
        // 用户列表响应
        UserListResponse: {
          type: 'object',
          properties: {
            code: { type: 'integer', example: 0 },
            msg: { type: 'string', example: '获取用户列表成功' },
            data: {
              type: 'object',
              properties: {
                total: { type: 'integer', example: 5 },
                rows: {
                  type: 'array',
                  items: {
                    type: 'object',
                    properties: {
                      id: { type: 'integer' },
                      username: { type: 'string' },
                      email: { type: 'string' },
                      role: { type: 'string' },
                      status: { type: 'integer' },
                      createTime: { type: 'string' },
                      lastLogin: { type: 'string' },
                    },
                  },
                },
                page: { type: 'integer', example: 1 },
                size: { type: 'integer', example: 10 },
                totalPages: { type: 'integer', example: 1 },
              },
            },
          },
        },
        // 用户信息
        User: {
          type: 'object',
          properties: {
            id: { type: 'integer' },
            username: { type: 'string' },
            email: { type: 'string' },
            role: { type: 'string' },
            status: { type: 'integer' },
            createTime: { type: 'string' },
            lastLogin: { type: 'string' },
          },
        },
        // 创建用户请求
        CreateUserRequest: {
          type: 'object',
          required: ['username', 'email'],
          properties: {
            username: { type: 'string', example: 'newuser' },
            email: { type: 'string', example: 'newuser@example.com' },
            role: { type: 'string', enum: ['admin', 'manager', 'user', 'developer'], example: 'user' },
          },
        },
      },
    },
    security: [
      {
        bearerAuth: [],
      },
    ],
    tags: [
      {
        name: '认证',
        description: '用户认证相关接口',
      },
      {
        name: '用户管理',
        description: '用户管理相关接口',
      },
    ],
  },
  apis: ['./src/routes/*.js'], // 扫描所有路由文件中的 JSDoc 注释
}

module.exports = swaggerJsdoc(options)

🚀 第三步:在 app.js 中集成 Swagger

修改 src/app.js

javascript 复制代码
// src/app.js
const express = require('express')
const cors = require('cors')
require('dotenv').config()
const swaggerUi = require('swagger-ui-express')
const swaggerSpec = require('./config/swagger')

// 导入路由
const authRoutes = require('./routes/auth')
const userRoutes = require('./routes/user')

const app = express()
const PORT = process.env.PORT || 3000

// 中间件
app.use(cors())
app.use(express.json())
app.use(express.urlencoded({ extended: true }))

// ===== 添加 Swagger UI =====
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerSpec, {
  explorer: true, // 显示搜索框
  customCss: '.swagger-ui .topbar { display: none }', // 隐藏顶部栏
  customSiteTitle: '用户管理 API 文档',
  swaggerOptions: {
    persistAuthorization: true, // 持久化认证信息
  },
}))

// 导出 Swagger 规范(可选,用于其他工具)
app.get('/api-docs.json', (req, res) => {
  res.setHeader('Content-Type', 'application/json')
  res.send(swaggerSpec)
})

// 路由
app.use('/api/auth', authRoutes)
app.use('/api/user', userRoutes)

// 健康检查
app.get('/health', (req, res) => {
  res.json({
    status: 'ok',
    timestamp: new Date().toISOString(),
    uptime: process.uptime(),
  })
})

// 404 处理
app.use((req, res) => {
  res.status(404).json({
    code: 404,
    msg: '接口不存在',
    data: null,
  })
})

// 全局错误处理
app.use((err, req, res, next) => {
  console.error('全局错误:', err)
  res.status(500).json({
    code: 500,
    msg: err.message || '服务器内部错误',
    data: null,
  })
})

app.listen(PORT, () => {
  console.log(`🚀 服务器已启动`)
  console.log(`📍 地址: http://localhost:${PORT}`)
  console.log(`📚 Swagger 文档: http://localhost:${PORT}/api-docs`)
  console.log(`🌿 环境: ${process.env.NODE_ENV || 'development'}`)
})

📝 第四步:在路由中添加 Swagger 注释

1. 认证路由注释

修改 src/routes/auth.js,添加 JSDoc 注释:

javascript 复制代码
// src/routes/auth.js
const express = require('express')
const router = express.Router()
const jwt = require('jsonwebtoken')
const bcrypt = require('bcryptjs')
const { findUserByUsername } = require('../utils/mockData')

/**
 * @swagger
 * /auth/login:
 *   post:
 *     summary: 用户登录
 *     description: 使用用户名和密码登录,返回 JWT Token
 *     tags: [认证]
 *     requestBody:
 *       required: true
 *       content:
 *         application/json:
 *           schema:
 *             $ref: '#/components/schemas/LoginRequest'
 *     responses:
 *       200:
 *         description: 登录成功
 *         content:
 *           application/json:
 *             schema:
 *               $ref: '#/components/schemas/LoginResponse'
 *       400:
 *         description: 请求参数错误
 *         content:
 *           application/json:
 *             schema:
 *               $ref: '#/components/schemas/ApiResponse'
 *       401:
 *         description: 用户名或密码错误
 *         content:
 *           application/json:
 *             schema:
 *               $ref: '#/components/schemas/ApiResponse'
 *       403:
 *         description: 用户已被禁用
 *         content:
 *           application/json:
 *             schema:
 *               $ref: '#/components/schemas/ApiResponse'
 */
router.post('/login', async (req, res) => {
  // ... 原有代码
})

/**
 * @swagger
 * /auth/verify:
 *   get:
 *     summary: 验证 Token
 *     description: 验证 JWT Token 是否有效
 *     tags: [认证]
 *     security:
 *       - bearerAuth: []
 *     responses:
 *       200:
 *         description: Token 有效
 *         content:
 *           application/json:
 *             schema:
 *               type: object
 *               properties:
 *                 code:
 *                   type: integer
 *                   example: 0
 *                 msg:
 *                   type: string
 *                   example: Token 有效
 *                 data:
 *                   type: object
 *                   properties:
 *                     id:
 *                       type: integer
 *                     username:
 *                       type: string
 *                     email:
 *                       type: string
 *                     role:
 *                       type: string
 *       401:
 *         description: 无效的 Token
 *         content:
 *           application/json:
 *             schema:
 *               $ref: '#/components/schemas/ApiResponse'
 */
router.get('/verify', (req, res) => {
  // ... 原有代码
})

module.exports = router

2. 用户路由注释

修改 src/routes/user.js

javascript 复制代码
// src/routes/user.js
const express = require('express')
const router = express.Router()
const { authenticate, authorize } = require('../middleware/auth')
const { generateUserList, findUserById } = require('../utils/mockData')

/**
 * @swagger
 * /user/list:
 *   get:
 *     summary: 获取用户列表
 *     description: 分页获取用户列表,支持搜索和筛选
 *     tags: [用户管理]
 *     security:
 *       - bearerAuth: []
 *     parameters:
 *       - in: query
 *         name: page
 *         schema:
 *           type: integer
 *           default: 1
 *         description: 页码
 *       - in: query
 *         name: size
 *         schema:
 *           type: integer
 *           default: 10
 *         description: 每页大小
 *       - in: query
 *         name: keyword
 *         schema:
 *           type: string
 *         description: 关键词搜索(用户名或邮箱)
 *       - in: query
 *         name: status
 *         schema:
 *           type: integer
 *           enum: [0, 1]
 *         description: 用户状态:0-禁用,1-启用
 *       - in: query
 *         name: role
 *         schema:
 *           type: string
 *           enum: [admin, manager, user, developer]
 *         description: 用户角色
 *     responses:
 *       200:
 *         description: 获取用户列表成功
 *         content:
 *           application/json:
 *             schema:
 *               $ref: '#/components/schemas/UserListResponse'
 *       400:
 *         description: 请求参数错误
 *         content:
 *           application/json:
 *             schema:
 *               $ref: '#/components/schemas/ApiResponse'
 *       401:
 *         description: 未登录或 Token 无效
 *         content:
 *           application/json:
 *             schema:
 *               $ref: '#/components/schemas/ApiResponse'
 */
router.get('/list', authenticate, (req, res) => {
  // ... 原有代码
})

/**
 * @swagger
 * /user/{id}:
 *   get:
 *     summary: 获取用户详情
 *     description: 根据用户 ID 获取详细信息
 *     tags: [用户管理]
 *     security:
 *       - bearerAuth: []
 *     parameters:
 *       - in: path
 *         name: id
 *         required: true
 *         schema:
 *           type: integer
 *         description: 用户 ID
 *     responses:
 *       200:
 *         description: 获取用户详情成功
 *         content:
 *           application/json:
 *             schema:
 *               type: object
 *               properties:
 *                 code:
 *                   type: integer
 *                   example: 0
 *                 msg:
 *                   type: string
 *                   example: 获取用户详情成功
 *                 data:
 *                   $ref: '#/components/schemas/User'
 *       401:
 *         description: 未登录
 *       404:
 *         description: 用户不存在
 *         content:
 *           application/json:
 *             schema:
 *               $ref: '#/components/schemas/ApiResponse'
 */
router.get('/:id', authenticate, (req, res) => {
  // ... 原有代码
})

/**
 * @swagger
 * /user/info:
 *   get:
 *     summary: 获取当前用户信息
 *     description: 获取当前登录用户的信息
 *     tags: [用户管理]
 *     security:
 *       - bearerAuth: []
 *     responses:
 *       200:
 *         description: 获取用户信息成功
 *         content:
 *           application/json:
 *             schema:
 *               type: object
 *               properties:
 *                 code:
 *                   type: integer
 *                   example: 0
 *                 msg:
 *                   type: string
 *                   example: 获取用户信息成功
 *                 data:
 *                   $ref: '#/components/schemas/User'
 *       401:
 *         description: 未登录
 */
router.get('/info', authenticate, (req, res) => {
  // ... 原有代码
})

/**
 * @swagger
 * /user:
 *   post:
 *     summary: 创建用户
 *     description: 创建新用户(需要管理员权限)
 *     tags: [用户管理]
 *     security:
 *       - bearerAuth: []
 *     requestBody:
 *       required: true
 *       content:
 *         application/json:
 *           schema:
 *             $ref: '#/components/schemas/CreateUserRequest'
 *     responses:
 *       200:
 *         description: 创建用户成功
 *         content:
 *           application/json:
 *             schema:
 *               type: object
 *               properties:
 *                 code:
 *                   type: integer
 *                   example: 0
 *                 msg:
 *                   type: string
 *                   example: 创建用户成功
 *                 data:
 *                   $ref: '#/components/schemas/User'
 *       400:
 *         description: 请求参数错误
 *       401:
 *         description: 未登录
 *       403:
 *         description: 权限不足
 */
router.post('/', authenticate, authorize(['admin']), (req, res) => {
  // ... 原有代码
})

/**
 * @swagger
 * /user/{id}:
 *   put:
 *     summary: 更新用户
 *     description: 更新用户信息(需要管理员权限)
 *     tags: [用户管理]
 *     security:
 *       - bearerAuth: []
 *     parameters:
 *       - in: path
 *         name: id
 *         required: true
 *         schema:
 *           type: integer
 *         description: 用户 ID
 *     requestBody:
 *       required: true
 *       content:
 *         application/json:
 *           schema:
 *             type: object
 *             properties:
 *               email:
 *                 type: string
 *                 example: newemail@example.com
 *               role:
 *                 type: string
 *                 enum: [admin, manager, user, developer]
 *               status:
 *                 type: integer
 *                 enum: [0, 1]
 *                 description: 用户状态:0-禁用,1-启用
 *     responses:
 *       200:
 *         description: 更新用户成功
 *         content:
 *           application/json:
 *             schema:
 *               type: object
 *               properties:
 *                 code:
 *                   type: integer
 *                   example: 0
 *                 msg:
 *                   type: string
 *                   example: 更新用户成功
 *                 data:
 *                   $ref: '#/components/schemas/User'
 *       401:
 *         description: 未登录
 *       403:
 *         description: 权限不足
 *       404:
 *         description: 用户不存在
 */
router.put('/:id', authenticate, authorize(['admin']), (req, res) => {
  // ... 原有代码
})

/**
 * @swagger
 * /user/{id}:
 *   delete:
 *     summary: 删除用户
 *     description: 删除指定用户(需要管理员权限)
 *     tags: [用户管理]
 *     security:
 *       - bearerAuth: []
 *     parameters:
 *       - in: path
 *         name: id
 *         required: true
 *         schema:
 *           type: integer
 *         description: 用户 ID
 *     responses:
 *       200:
 *         description: 删除用户成功
 *         content:
 *           application/json:
 *             schema:
 *               $ref: '#/components/schemas/ApiResponse'
 *       401:
 *         description: 未登录
 *       403:
 *         description: 权限不足
 *       404:
 *         description: 用户不存在
 */
router.delete('/:id', authenticate, authorize(['admin']), (req, res) => {
  // ... 原有代码
})

module.exports = router

🌐 第五步:访问 Swagger UI

启动服务后,在浏览器访问:

复制代码
http://localhost:3000/api-docs

📖 第六步:Swagger UI 使用指南

1. 界面布局

Swagger UI 界面主要包含:

  • 顶部搜索框:搜索 API 路径或描述
  • 左侧导航:按 Tags 分组显示所有接口
  • 右侧内容:显示选中接口的详细信息

2. 认证设置

对于需要 Token 的接口(如 /user/list),需要先设置认证:

  1. 点击页面右上角的 "Authorize" 按钮
  2. 在弹出的对话框中输入:Bearer <你的Token>
  3. 点击 "Authorize" 按钮
  4. 关闭对话框

3. 测试接口

在 Swagger UI 中可以直接测试接口:

  1. 展开想要测试的接口
  2. 点击 "Try it out" 按钮
  3. 填写请求参数
  4. 点击 "Execute" 按钮
  5. 查看响应结果

4. 查看响应示例

每个接口下方会显示:

  • 请求示例:Curl 命令格式
  • 响应示例:JSON 格式的响应数据
  • 响应状态码:不同状态码的含义

🎨 第七步:自定义 Swagger UI 样式(可选)

创建 src/config/swagger.js 增强配置:

javascript 复制代码
// src/config/swagger.js
const swaggerJsdoc = require('swagger-jsdoc')

// 自定义 CSS 样式
const customCss = `
  .swagger-ui .topbar { 
    background-color: #2a3f54; 
    padding: 10px 0;
  }
  .swagger-ui .topbar .download-url-wrapper .select-label {
    display: none;
  }
  .swagger-ui .info .title {
    color: #2a3f54;
  }
  .swagger-ui .btn.authorize {
    border-color: #2a3f54;
    color: #2a3f54;
  }
  .swagger-ui .btn.authorize:hover {
    background-color: #2a3f54;
    color: #fff;
  }
`

// 在 app.js 中使用
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerSpec, {
  explorer: true,
  customCss: customCss,
  customSiteTitle: '用户管理 API 文档',
  swaggerOptions: {
    persistAuthorization: true,
    docExpansion: 'none', // 默认折叠所有接口:'none' | 'list' | 'full'
    filter: true, // 启用过滤器
    showExtensions: true,
    showCommonExtensions: true,
    syntaxHighlight: {
      activated: true,
      theme: 'monokai',
    },
  },
}))

📄 第八步:导出 API 文档(Markdown/PDF)

方案一:使用 swagger-to-markdown

bash 复制代码
npm install swagger-to-markdown -g
bash 复制代码
# 导出为 Markdown
swagger-to-markdown http://localhost:3000/api-docs.json > API_DOCS.md

方案二:使用 Swagger Editor 导出

  1. 访问 Swagger Editor
  2. 导入 http://localhost:3000/api-docs.json
  3. 点击 "Generate Client" → 选择格式(HTML/PDF 等)

🔧 第九步:常见问题解决

问题1:Swagger 页面空白

原因:可能是 CORS 或路由配置问题。

解决

javascript 复制代码
// 确保 CORS 配置正确
app.use(cors())

// 确保 Swagger UI 路由在其他路由之前
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerSpec))

问题2:JSDoc 注释没有被解析

原因apis 路径配置不正确。

解决

javascript 复制代码
// 在 swagger.js 中检查 apis 配置
apis: ['./src/routes/*.js'], // 确保路径正确

问题3:Swagger 中显示不了中文

解决

javascript 复制代码
// 在 package.json 中添加
{
  "scripts": {
    "dev": "nodemon --exec \"node -r ./src/config/swagger.js src/app.js\""
  }
}

📊 第十步:完整效果预览

启动服务后,访问 http://localhost:3000/api-docs,你将看到:

  1. API 分组:认证、用户管理
  2. 接口列表:所有接口清晰展示
  3. 参数说明:每个参数的详细说明
  4. 在线测试:直接在界面上测试接口
  5. 响应示例:查看预期的响应格式
  6. 认证集成:Bearer Token 认证

🎯 总结

使用 Swagger 的好处:

  1. 可视化文档:自动生成美观的 API 文档
  2. 在线测试:直接测试接口,无需 Postman
  3. 标准化:遵循 OpenAPI 规范
  4. 交互式:支持参数填写和响应查看
  5. 易于维护:代码即文档,保持同步
  6. 团队协作:方便前后端联调

现在你的后端服务不仅有完整的接口功能,还有专业的 API 文档,便于前端开发和团队协作!

相关推荐
FungLeo7 小时前
成为全栈·Node 后端篇·全文搜索:从 LIKE 到全文索引
node.js·全文索引·模糊搜索·全文搜索·成为全栈·like 搜索
倾颜1 天前
AI Chat 长会话性能实践:消息虚拟化、动态高度与流式滚动设计
前端·react.js·node.js
FungLeo1 天前
成为全栈·Node 后端篇·接口文档自动化:让 OpenAPI 与代码不脱节
node.js·openapi·成为全栈·接口文档自动化
右耳朵猫AI1 天前
Node.js周刊2026W36 | NestJS 12发布、Remix 3 RC、pnpm 12 Rust重写、Node.js 26.8.0
开发语言·rust·node.js
www_aiyuanma_vip1 天前
实验室设备借用系统
node.js
右耳朵猫AI1 天前
Web前端周刊2026W36 | pnpm 12 Rust 重写、Remix 3 RC、Node.js 26.8.0、htmx 4.0 大版本
前端·rust·node.js
FungLeo2 天前
成为全栈·Node 后端篇·文件上传:R2 / 本地磁盘双实现与签名直传
node.js·文件上传·成为全栈·cloudflare r2·文件去重
FungLeo2 天前
成为全栈·Node 后端篇·认证方案:JWT 还是 Session
node.js·jwt·session·认证方案
FungLeo2 天前
成为全栈·Node 后端篇·文章 CRUD 与投稿状态机
node.js·状态机·crud·成为全栈