使用 Monorepo 实现 Next.js 后端API 接口定义和移动端同步

3 阅读5分钟

在 Monorepo 中统一管理 Next.js 后端 API 与 Flutter 移动端项目:实现 API 接口定义的同步

之前在两个项目中分别管理接口实现和使用,在手工同步 API 定义的过程中吃了不少亏。后发现 Monorepo,于是在新项目进行尝试。

手动维护两套 API 客户端代码不仅繁琐,而且极易出错。本文将介绍一种优雅的解决方案:通过 Monorepo 架构和自动化工具,实现 API 接口定义的“一次编写,处处使用”。

为什么选择 Monorepo?

Monorepo(单体仓库)是一种将多个相关项目的代码存储在同一个代码仓库中的策略。对于我们的场景,即一个 Next.js 后端 API 和一个或多个 Flutter 应用,Monorepo 带来了显著的优势:

  • 依赖共享:公共的配置文件、工具库,甚至 API 类型定义,都可以被所有项目轻松引用。
  • 原子提交:对 API 的修改和对客户端的适配可以在同一个提交中完成,保证了代码库的一致性。

核心思路:API 契约优先

我们的核心策略是采用“契约优先”的开发模式。这意味着我们将 API 的接口定义(使用 OpenAPI/Swagger 规范)视为前后端之间的“契约”。

  1. 后端(Next.js):负责编写业务逻辑,并自动生成一份最新的 openapi.json 文件。
  2. 共享层(packages/api-spec):存放这份 openapi.json “契约”文件。
  3. 客户端(Flutter):监听“契约”的变化,并自动生成对应的 Dart 模型和网络请求代码。

通过这种方式,Flutter 客户端的代码始终与 Next.js 后端的 API 保持同步,从根本上杜绝了因接口不一致导致的运行时错误。

项目结构

一个典型的 Monorepo 项目结构如下所示:

my-fullstack-app/
├── apps/
│   ├── nextjs-api/          # Next.js 后端 API 项目
│   │   ├── pages/
│   │   │   └── api/
│   │   │       └── users.ts # API 路由
│   │   ├── scripts/
│   │   │   └── generate-spec.ts # 生成 OpenAPI 规范的脚本
│   │   └── package.json
│   └── flutter-app/         # Flutter 移动端应用
│       ├── lib/
│       │   ├── generated/   # 自动生成的 Dart 代码目录
│       │   └── main.dart
│       └── pubspec.yaml
├── packages/
│   └── api-spec/            # 共享的 API 规范包
│       └── openapi.json     # 核心的“契约”文件
├── package.json             # 根目录 package.json (用于工作区管理)
└── pnpm-workspace.yaml      # pnpm 工作区配置

第一步:配置 Next.js 后端生成 API 规范

Next.js 本身不提供 OpenAPI 规范生成功能,但我们可以借助 next-swagger-doc 等库来实现。

  1. 安装依赖:在 apps/nextjs-api 目录下安装必要的包。

    cd apps/nextjs-api
    npm install next-swagger-doc swagger-jsdoc
    
  2. 编写 API 路由和文档注释:在你的 API 路由文件(如 pages/api/users.ts)中,使用 JSDoc 风格的注释来描述接口。

    // apps/nextjs-api/pages/api/users.ts
    import { NextApiRequest, NextApiResponse } from 'next';
    import swaggerJsdoc from 'swagger-jsdoc';
    
    // 定义 OpenAPI 规范的元数据
    const options = {
      definition: {
        openapi: '3.0.0',
        info: {
          title: 'My App API',
          version: '1.0.0',
        },
      },
      apis: ['./pages/api/*.ts'], // 扫描所有 API 路由文件
    };
    const openapiSpecification = swaggerJsdoc(options);
    
    /**
     * @swagger
     * /api/users:
     *   get:
     *     summary: 获取所有用户列表
     *     tags: [Users]
     *     responses:
     *       200:
     *         description: 返回一个用户对象数组
     *         content:
     *           application/json:
     *             schema:
     *               type: array
     *               items:
     *                 type: object
     *                 properties:
     *                   id:
     *                     type: string
     *                   name:
     *                     type: string
     *                   email:
     *                     type: string
     */
    export default function handler(req: NextApiRequest, res: NextApiResponse) {
      if (req.method === 'GET') {
        // 模拟从数据库获取数据
        const users = [
          { id: '1', name: 'Alice', email: 'alice@example.com' },
          { id: '2', name: 'Bob', email: 'bob@example.com' },
        ];
        return res.status(200).json(users);
      }
      res.status(405).json({ message: 'Method Not Allowed' });
    }
    
  3. 创建生成脚本:编写一个脚本,在开发或构建时生成 openapi.json 文件,并将其复制到共享的 packages/api-spec 目录。

    // apps/nextjs-api/scripts/generate-spec.ts
    import * as fs from 'fs';
    import * as path from 'path';
    import swaggerJsdoc from 'swagger-jsdoc';
    
    const options = {
      definition: {
        openapi: '3.0.0',
        info: { title: 'My App API', version: '1.0.0' },
      },
      apis: ['./pages/api/*.ts'],
    };
    
    const openapiSpecification = swaggerJsdoc(options);
    
    // 将生成的规范写入到共享包中
    const specPath = path.join(__dirname, '../../packages/api-spec/openapi.json');
    fs.writeFileSync(specPath, JSON.stringify(openapiSpecification, null, 2));
    
    console.log('✅ OpenAPI specification generated successfully!');
    
  4. 更新 package.json:添加一个脚本来运行上述生成命令。

    // apps/nextjs-api/package.json
    {
      "scripts": {
        "dev": "next dev",
        "build": "npm run generate-spec && next build",
        "generate-spec": "ts-node scripts/generate-spec.ts"
      }
    }
    

第二步:配置 Flutter 客户端自动生成代码

Flutter 社区有强大的工具来根据 OpenAPI 规范生成代码,我们使用 openapi-generator

  1. 安装 OpenAPI Generator:最简单的方式是通过 npm 全局安装。

    npm install -g @openapitools/openapi-generator-cli
    
  2. 配置生成器:在 apps/flutter-app 目录下创建一个配置文件 openapi-generator-config.json

    // apps/flutter-app/openapi-generator-config.json
    {
      "packageName": "my_app_api_client",
      "pubName": "my_app_api_client",
      "pubVersion": "1.0.0",
      "pubDescription": "API client generated from OpenAPI spec",
      "pubAuthor": "Your Name"
    }
    
  3. 创建代码生成脚本:编写一个 shell 脚本,该脚本会从共享的 packages/api-spec 目录读取 openapi.json,并生成 Dart 代码到 lib/generated 目录。

    #!/bin/bash
    # apps/flutter-app/scripts/generate-client.sh
    
    SPEC_FILE=../../packages/api-spec/openapi.json
    OUTPUT_DIR=lib/generated
    
    echo "🚀 Generating Flutter API client from $SPEC_FILE..."
    
    openapi-generator-cli generate \
      -i $SPEC_FILE \
      -g dart-dio \
      -o $OUTPUT_DIR \
      -c openapi-generator-config.json
    
    echo "✅ Flutter API client generated successfully in $OUTPUT_DIR!"
    

    请确保给这个脚本执行权限:chmod +x apps/flutter-app/scripts/generate-client.sh

  4. 更新 pubspec.yaml:添加一个脚本来方便地运行生成命令。

    # apps/flutter-app/pubspec.yaml
    name: flutter_app
    description: A new Flutter project.
    version: 1.0.0+1
    
    environment:
      sdk: '>=3.0.0 <4.0.0'
    
    dependencies:
      flutter:
        sdk: flutter
      # 引入生成的 API 客户端包
      my_app_api_client:
        path: lib/generated
    
    dev_dependencies:
      flutter_test:
        sdk: flutter
      flutter_lints: ^2.0.0
    
    scripts: # 需要安装 flutter_scripts 或类似工具,或者直接用 shell 命令
      generate-client: sh scripts/generate-client.sh
    

工作流:让一切自动运转

完成以上配置后,一个高效、无错的开发工作流就形成了:

  1. 后端开发:开发者在 apps/nextjs-api 中修改或新增 API 路由,并更新相应的 JSDoc 注释。
  2. 生成契约:运行 npm run generate-spec(或在 build 时自动运行)。packages/api-spec/openapi.json 文件被更新。
  3. 生成客户端:切换到 apps/flutter-app 目录,运行 npm run generate-clientlib/generated 目录下的 Dart 代码被自动更新。
  4. 前端开发:Flutter 开发者现在可以直接使用新生成的、类型安全的 API 客户端代码进行开发,无需手动编写任何网络请求代码。

总结

通过在 Monorepo 中实施这种“契约优先”的策略,我们成功地解决了 Next.js 和 Flutter 之间的 API 同步难题。这不仅极大地提升了开发效率,减少了人为错误,还使得个人开发中前后端项目切换更加顺畅。后端更新后,移动端能立即获得最新、最准确的客户端代码,真正实现了高效的全栈开发体验。