阅读视图

发现新文章,点击刷新页面。

Dart Frog 实战系列(二):从零构建 REST API(Todo 应用全增删改查)

嘿,大家好!欢迎回到我们的 Dart Frog 实战系列第二期。如果你还没看过[第一期],建议先去补补课——在那一集里,我们完成了 Dart Frog 的环境搭建,并亲手跑通了一个支持热重载的基础 API。

今天,我们要玩点硬核的:我们将不再停留在基础阶段,而是要亲手构建你的第一个“实战级”REST API —— 一个基于纯 Dart 开发、架构整洁且生产就绪的 Todo 全功能后端。

欢迎关注我的微信公众号:OpenFlutter

我们会深入探讨动态路由、UUID 生成、数据校验以及规范的错误处理。在本篇结束时,你将拥有一套完整的、可测试的 API 接口,为下一期视频中对接 Flutter 应用做好全副武装。话不多说,我们直接开搞!

规划与最佳实践

核心思路: 我们将创建一个 Todo 模型,包含 ID、标题(Title)和完成状态(Completed status)。我们会先将数据存储在内存中(使用 Map 以实现快速查找)——这种方式非常适合上手学习,而且以后想要升级到 Postgres 或 Drift 等数据库也易如反掌。

我们要遵循的开发规范:

  • 动态路由:利用 [id].dart 实现灵活路径。
  • UUID 库:确保每一个任务都有唯一的身份证。
  • 请求体校验:对接收到的 JSON 数据进行严格检查。
  • 规范的状态码:准确返回 200(成功)、201(已创建)、404(未找到)和 400(请求错误)。

准备工作: 打开你在第一部分中创建的项目,或者直接运行 dart_frog create todo_api 新建一个。别忘了启动 dart_frog dev

第一步,引入 UUID 依赖。编辑 pubspec.yaml

dependencies:  
  uuid: ^4.5.0

运行 flutter pub get(或者 dart pub get)。

第一步:创建数据模型 lib/src/todo.dart:


///
class Todo {
  ///
  Todo({required this.id, required this.title, this.isCompleted = false});

  /// fromJson
  factory Todo.fromJson(Map<String, dynamic> json) {
    return Todo(
      id: json['id'] as String,
      title: json['title'] as String,
      isCompleted: json['isCompleted'] as bool? ?? false,
    );
  }

  /// id
  final String id;

  /// title
  final String title;

  /// isCompleted
  bool isCompleted;

  /// toJson
  Map<String, dynamic> toJson() {
    return {
      'id': id,
      'title': title,
      'isCompleted': isCompleted,
    };
  }
}

第二步:内存存储实现 lib/src/todo_repository.dart:

import 'package:my_project/src/todo_model.dart';
import 'package:uuid/uuid.dart';

const _uuid = Uuid();
final _todos = <String, Todo>{};

/// get all todos
List<Todo> getAllTodos() => _todos.values.toList();

/// get a tod
Todo? getTodoById(String id) => _todos[id];

/// create
void createTodo(String title) {
  final id = _uuid.v4();
  _todos[id] = Todo(id: id, title: title);
}

/// update
void updateTodo(String id, {String? title, bool? isCompleted}) {
  final todo = _todos[id];
  if (todo == null) return;
  _todos[id] = Todo(
    id: id,
    title: title ?? todo.title,
    isCompleted: isCompleted ?? todo.isCompleted,
  );
}

/// delete
void deleteTodo(String id) => _todos.remove(id);

现在开始撸路由!

集合接口:routes/todos/index.dart

import 'package:dart_frog/dart_frog.dart';
import 'package:my_project/src/todo_repository.dart';

Future<Response> onRequest(RequestContext context) async {
  switch (context.request.method) {
    case HttpMethod.get:
      final todos = getAllTodos();
      return Response.json(body: todos.map((e) => e.toJson()).toList());
    case HttpMethod.post:
      final body = await context.request.json() as Map<String, dynamic>;
      final title = body['title'] as String?;
      if (title == null || title.isEmpty) {
        return Response(statusCode: 400, body: 'Title is required');
      }
      createTodo(title);
      return Response(statusCode: 201, body: 'Todo created');
    case HttpMethod.delete:
    case HttpMethod.put:
    case HttpMethod.patch:
    case HttpMethod.head:
    case HttpMethod.options:
      return Response(statusCode: 405);
  }
}

动态单项接口:routes/todos/[id].dart

import 'package:dart_frog/dart_frog.dart';
import 'package:my_project/src/todo_repository.dart';

Future<Response> onRequest(RequestContext context, String id) async {
  final todo = getTodoById(id);
  if (todo == null) return Response(statusCode: 404);

  switch (context.request.method) {
    case HttpMethod.get:
      return Response.json(body: todo.toJson());
    case HttpMethod.put:
      final body = await context.request.json() as Map<String, dynamic>;
      final title = body['title'] as String?;
      final isCompleted = body['isCompleted'] as bool?;
      updateTodo(id, title: title, isCompleted: isCompleted);
      return Response.json(body: getTodoById(id)!.toJson());
    case HttpMethod.delete:
      deleteTodo(id);
      return Response(statusCode: 204);
    case HttpMethod.post:
    case HttpMethod.patch:
    case HttpMethod.head:
    case HttpMethod.options:
      return Response(statusCode: 405);
  }
}

测试与总结(演示 curl/Postman)

代码撸完了,是时候看看成果了。我们将通过几个快速测试来验证接口是否按预期工作。

curl http://localhost:8080/todos  
curl -X POST http://localhost:8080/todos -H "Content-Type: application/json" -d '{"title": "Learn Dart Frog"}'  
curl http://localhost:8080/todos/<generated-id>

优雅地处理错误。这套架构将是你构建“生产就绪”后端的坚实地基!

源码地址 👇 —— 如果对你有帮助,请给仓库点个星 ⭐ (Star) 并在社交平台关注我 😄! github.com/techwithsam…

这就是你的第一个实战级 Dart Frog REST API —— 恭喜通关!下一站:我们将亲手把 Flutter App 对接到这套后端上。

2026 年的 Dart 服务端开发:Dart Frog 入门指南

2026 年我的计划之一,就是深入探索 Dart 在服务端的潜力。 如今,Flutter 已稳坐跨平台开发的头把交椅,驱动着全球超过 40% 的移动端与 Web 新应用。然而,真正能改变行业游戏规则的,是实现**“全栈 Dart”开发** —— 即在客户端和服务端使用同一种语言,构建整套系统。

在接下来的指南(以及配套视频)中,我们将一起探索 Dart Frog。这款极简的后端框架正迅速捕获 Flutter 开发者的心。我们将从零开始搭建一个简单的 REST API,并探讨为什么在实际的 Flutter 项目中,它的表现往往优于传统的 Node.js 或 Express。

为什么 2026 年 Dart 服务端开发至关重要? Flutter 生态已经高度成熟。开发者们不再希望在客户端用着 Dart,转头写后端又要切换到 JavaScript/TypeScript。全栈 Dart 的核心优势在于:

  • 代码共用:数据模型(Models)、枚举(Enums)以及校验逻辑完全复用。
  • 全栈空安全:从数据库到 UI,享受贯穿始终的类型安全。
  • 研发提速:更少的 Bug,更快的开发周期。

目前的流行方案包括:底层框架 Shelf、全家桶级框架 Serverpod,以及 Dart Frog —— 后者是大多数 REST API 和微服务的最佳平衡点(Sweet Spot)

什么是 Dart Frog?

Dart Frog 是一个受 Express.js 启发、完全基于路由的极简后端框架。它最初由 Very Good Ventures 创建,现已发展成为一个蓬勃发展的社区驱动项目。

核心特性:

  • 后端代码热重载:修改代码,即刻生效。
  • 强大的 CLI 工具:一键生成项目脚手架。
  • AOT 编译:直接生成高性能的生产环境原生二进制文件。
  • 内置中间件支持
  • 部署便捷:支持 Dart Globe、Vercel 等主流平台。

Dart Frog vs Node.js:性能与生态

虽然两者都擅长处理 I/O 密集型 API,但在以 Flutter 为核心的项目中,Dart Frog 往往更胜一筹:

  • AOT 编译:带来更低的延迟和更快的启动速度。
  • Dart Isolates:实现真正的并发处理(对比 Node.js 的单线程事件循环)。
  • 高效的数据处理:更快的 JSON 处理能力和更低的运行时开销。

最近的社区测试证实,Dart 在 CPU 密集型任务和原生部署方面具备明显优势。虽然 Node.js 在某些场景下的纯吞吐量依然领先,但对于 Flutter 开发者来说, “一套代码走天下”的巨大优势让 Dart Frog 成为了更理智的选择。

动手实现:你的第一个 Dart Frog API

我们来创建一个简单的用户接口。

1.安装 CLI 工具:

dart pub global activate dart_frog_cli

2. 创建并运行项目:

接下来,我们正式开始搭建。只需几行简单的命令,你就能让服务器跑起来:

dart_frog create my_backend  
cd my_backend  
dart_frog dev

3. 默认路由 (routes/index.dart):

import 'package:dart_frog/dart_frog.dart';

Response onRequest(RequestContext context) {
  return Response.json(
    body: {'message': 'Hello Flutter World from Dart Frog! 🐸'},
  );
}

4. 用户接口路由 (routes/users/index.dart):

import 'package:dart_frog/dart_frog.dart';

final _users = <Map<String, dynamic>>[
  {'id': 1, 'name': 'Alice'},
  {'id': 2, 'name': 'Bob'},
  {'id': 3, 'name': 'Charlie'},
];

Future<Response> onRequest(RequestContext context) async {
  // get method to fetch users
  if (context.request.method == HttpMethod.get) {
    return Response.json(body: _users);
  }

  // post method to add a new user
  if (context.request.method == HttpMethod.post) {
    final body = await context.request.json() as Map<String, dynamic>;

    final newUser = {
      'id': _users.length + 1,
      'name': body['name'],
    };
    _users.add(newUser);
    return Response.json(body: newUser, statusCode: 201);
  }

  return Response(statusCode: 405);
}

使用curl测试:

curl http://localhost:8080/users
curl -X POST http://localhost:8080/users -H "Content-Type: application/json" -d '{"name": "Samuel"}'

生产环境部署:

dart_frog build

接下来还有哪些精彩内容? 这仅仅是个开始。在本系列后续的内容中,我们将深入探讨:

  • 实战数据库集成:让你的数据持久化。
  • 对接 Flutter 前端:实现真正的全栈联动。
  • 认证与部署:从安全校验到线上发布。
  • 扩容与进阶特性:应对大规模流量的黑科技。

此处查看完整源码

你目前正在使用哪种后端方案?欢迎在评论区分享你的看法!

❌