07-Flutter+Dart后端全栈实战-DartFrog打通前后端

0 阅读5分钟

🔥 Flutter + Dart 后端全栈实战:用 Dart Frog 打通前后端

前端写 Dart,后端也写 Dart — 一个语言吃到底。Dart Frog 是 Dart 生态的后端框架, 让 Flutter 开发者无需学新语言就能搭建 API 服务。共享数据模型、统一类型安全, 这才是真正的全栈。

核心优势:前后端共享 Dart 数据模型 + 类型系统,接口联调零沟通成本, 一个团队搞定全部。


📊 Dart 后端方案对比

框架出品方定位特点GitHub Star
🐸 Dart FrogVery Good VenturesAPI 服务极简、约定式路由、中间件2k+
🚀 ServerpodServerpod team全栈框架ORM、认证、WebSocket 内置2.5k+
🏗 shelfDart 官方HTTP 库极底层,需自己搭建Dart SDK 内置
⚡ ConduitStable KernelREST API类似 Spring Boot维护中

🔑 为什么选 Dart Frog? 它是最轻量、上手最快的选择。 文件路由约定(类似 Next.js),中间件系统完善,与 Flutter 天然配合。


🛠 1. 环境搭建与项目创建

# 安装 Dart Frog CLI
dart pub global activate dart_frog_cli

# 创建后端项目
dart_frog create my_api_server
cd my_api_server

# 启动开发服务器(支持热重载)
dart_frog dev
# 🚀 服务运行在 http://localhost:8080

项目结构

my_api_server/
├── routes/                    # 📂 文件路由(核心!)
│   ├── index.dart            # GET /
│   ├── health.dart           # GET /health
│   ├── api/
│   │   ├── products/
│   │   │   ├── index.dart    # GET|POST /api/products
│   │   │   └── [id].dart     # GET|PUT|DELETE /api/products/:id
│   │   └── auth/
│   │       ├── login.dart    # POST /api/auth/login
│   │       └── register.dart # POST /api/auth/register
│   └── _middleware.dart      # 全局中间件
├── lib/                      # 共享业务逻辑
│   ├── models/               # 数据模型
│   ├── repositories/         # 数据层
│   └── services/             # 业务服务
├── test/                     # 测试
├── pubspec.yaml
└── dart_frog.yaml            # 配置文件

🔑 文件即路由routes/api/products/index.dartGET /api/productsroutes/api/products/[id].dartGET /api/products/:id。零配置!


🧩 2. API 开发:RESTful 接口

商品列表接口

// routes/api/products/index.dart
import 'dart:io';
import 'package:dart_frog/dart_frog.dart';

Future<Response> onRequest(RequestContext context) async {
  switch (context.request.method) {
    case HttpMethod.get:
      return _getProducts(context);
    case HttpMethod.post:
      return _createProduct(context);
    default:
      return Response(statusCode: HttpStatus.methodNotAllowed);
  }
}

Future<Response> _getProducts(RequestContext context) async {
  final repo = context.read<ProductRepository>();
  final params = context.request.uri.queryParameters;
  final page = int.tryParse(params['page'] ?? '1') ?? 1;
  final pageSize = int.tryParse(params['pageSize'] ?? '20') ?? 20;

  final result = await repo.getProducts(page: page, pageSize: pageSize);

  return Response.json(body: {
    'code': 0,
    'data': {
      'list': result.items.map((e) => e.toJson()).toList(),
      'total': result.total,
      'page': page,
    },
  });
}

Future<Response> _createProduct(RequestContext context) async {
  final repo = context.read<ProductRepository>();
  final body = await context.request.json() as Map<String, dynamic>;
  final product = Product.fromJson(body);
  final created = await repo.create(product);

  return Response.json(
    statusCode: HttpStatus.created,
    body: {'code': 0, 'data': created.toJson()},
  );
}

商品详情接口(动态路由)

// routes/api/products/[id].dart
import 'dart:io';
import 'package:dart_frog/dart_frog.dart';

Future<Response> onRequest(RequestContext context, String id) async {
  switch (context.request.method) {
    case HttpMethod.get:
      return _getProduct(context, id);
    case HttpMethod.put:
      return _updateProduct(context, id);
    case HttpMethod.delete:
      return _deleteProduct(context, id);
    default:
      return Response(statusCode: HttpStatus.methodNotAllowed);
  }
}

Future<Response> _getProduct(RequestContext context, String id) async {
  final repo = context.read<ProductRepository>();
  final product = await repo.getById(id);

  if (product == null) {
    return Response.json(
      statusCode: HttpStatus.notFound,
      body: {'code': 404, 'message': '商品不存在'},
    );
  }

  return Response.json(body: {'code': 0, 'data': product.toJson()});
}

🔗 3. 前后端共享数据模型(核心价值)

Monorepo 结构

my_fullstack_app/
├── packages/
│   └── shared_models/           # 🔥 共享包!
│       ├── lib/
│       │   ├── src/
│       │   │   ├── product.dart
│       │   │   └── user.dart
│       │   └── shared_models.dart
│       └── pubspec.yaml
├── api_server/                  # Dart Frog 后端
│   └── pubspec.yaml             # 依赖 shared_models
├── flutter_app/                 # Flutter 前端
│   └── pubspec.yaml             # 依赖 shared_models

共享模型定义

// packages/shared_models/lib/src/product.dart
import 'package:freezed_annotation/freezed_annotation.dart';

part 'product.freezed.dart';
part 'product.g.dart';

@freezed
class Product with _$Product {
  const factory Product({
    required String id,
    required String name,
    required double price,
    @Default('') String image,
    @Default('') String description,
    @Default(0) int stock,
    DateTime? createdAt,
  }) = _Product;

  factory Product.fromJson(Map<String, dynamic> json) =>
      _$ProductFromJson(json);
}

@freezed
class ProductListResponse with _$ProductListResponse {
  const factory ProductListResponse({
    required List<Product> list,
    required int total,
    required int page,
  }) = _ProductListResponse;

  factory ProductListResponse.fromJson(Map<String, dynamic> json) =>
      _$ProductListResponseFromJson(json);
}

前后端引用共享包

# api_server/pubspec.yaml
dependencies:
  dart_frog: ^1.1.0
  shared_models:
    path: ../packages/shared_models

# flutter_app/pubspec.yaml
dependencies:
  flutter_riverpod: ^3.2.1
  shared_models:
    path: ../packages/shared_models

共享模型的威力

维度✅ 共享模型❌ 各端自定义
字段一致性编译期保证完全一致手动同步,容易遗漏
JSON 序列化一份 fromJson / toJson两端各写一份
重构安全改一处,两端编译报错改一端忘改另一端
联调成本几乎为零反复沟通确认字段

🛡 4. 中间件:认证、日志、CORS

// routes/_middleware.dart(全局中间件)
import 'package:dart_frog/dart_frog.dart';

Handler middleware(Handler handler) {
  return handler
      .use(loggerMiddleware())       // 日志
      .use(corsMiddleware())         // CORS
      .use(authMiddleware())         // 认证
      .use(rateLimitMiddleware());   // 限流
}

// 日志中间件
Middleware loggerMiddleware() {
  return (handler) {
    return (context) async {
      final stopwatch = Stopwatch()..start();
      final response = await handler(context);
      stopwatch.stop();

      print('[${context.request.method.name}] '
          '${context.request.uri.path} '
          '→ ${response.statusCode} '
          '(${stopwatch.elapsedMilliseconds}ms)');

      return response;
    };
  };
}

// CORS 中间件
Middleware corsMiddleware() {
  return (handler) {
    return (context) async {
      if (context.request.method == HttpMethod.options) {
        return Response(statusCode: 204, headers: _corsHeaders);
      }
      final response = await handler(context);
      return response.copyWith(headers: {...response.headers, ..._corsHeaders});
    };
  };
}

const _corsHeaders = {
  'Access-Control-Allow-Origin': '*',
  'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
  'Access-Control-Allow-Headers': 'Content-Type, Authorization',
};

// JWT 认证中间件
Middleware authMiddleware() {
  return (handler) {
    return (context) async {
      final path = context.request.uri.path;

      // 跳过不需要认证的路径
      if (path.startsWith('/api/auth/') || path == '/health') {
        return handler(context);
      }

      final authHeader = context.request.headers['Authorization'];
      if (authHeader == null || !authHeader.startsWith('Bearer ')) {
        return Response.json(
          statusCode: 401,
          body: {'code': 401, 'message': '未登录'},
        );
      }

      final token = authHeader.substring(7);
      final user = await verifyJwt(token);
      if (user == null) {
        return Response.json(
          statusCode: 401,
          body: {'code': 401, 'message': 'Token 已过期'},
        );
      }

      // 注入用户信息到 context
      return handler(context.provide<User>(() => user));
    };
  };
}

🚀 5. 部署上线

Docker 部署

# Dockerfile
FROM dart:stable AS build

WORKDIR /app
COPY . .
RUN dart pub get
RUN dart_frog build

FROM scratch
COPY --from=build /runtime/ /
COPY --from=build /app/build/ /app/

EXPOSE 8080
ENTRYPOINT ["/app/bin/server"]
# 构建并运行
docker build -t my-api-server .
docker run -p 8080:8080 my-api-server

部署对比

方案适合场景成本
Docker + VPS完全自控¥50-200/月
Cloud Run (GCP)按需伸缩按请求量
Railway / Fly.io快速部署免费额度 + 按量
Vercel EdgeServerless免费额度

💉 6. 常见踩坑

错误后果修复
共享包没有 dart run build_runnerfromJson 缺失,序列化报错共享包也要运行代码生成
中间件顺序错误认证在日志前,看不到被拦截的请求日志 → CORS → 认证 → 限流
文件路由忘记导出 onRequest404 找不到接口确保函数名为 onRequest
Flutter 端直连 localhost真机无法访问用内网 IP 或 ngrok 隧道

✅ Dart 全栈 Checklist

后端搭建

  • 安装 Dart Frog CLI
  • 创建文件路由结构
  • 配置中间件(日志 / CORS / 认证)
  • 实现核心 CRUD 接口

前后端共享

  • 创建 shared_models 包
  • Freezed 定义共享数据模型
  • 前后端 pubspec 引用共享包
  • 验证 JSON 序列化一致性

部署

  • 编写 Dockerfile
  • 配置环境变量
  • CI/CD 流水线
  • 监控与日志

全栈的终极形态,不是"什么都会",而是"用最少的技术栈做最多的事"。 Dart 全栈就是这种极简主义的实践 — 一门语言、一套类型、一个团队, 从用户点击按钮到数据库返回结果,全链路类型安全