用Dart写服务端:Flutter工程师的进阶指南
作为一名Flutter开发者,已经熟练掌握了打造漂亮、高性能用户界面的技巧。Widget、状态管理、Dart语言,早就玩得得心应手。但如果需要从服务端拉数据、验证用户身份,或者跑一些不适合放在客户端的逻辑,怎么办?
以前,这通常意味着把活儿甩给后端团队,或者硬着头皮去学一门新语言和框架,比如Node.js、Python或Go。
但如果告诉你,你可以用自己已经熟悉的Dart语言,从前端按钮到后端数据库查询,全程一条龙搞定,会不会很爽?
为啥要学服务端Dart?
直接说重点:为啥你一个忙得飞起的Flutter工程师,要花时间学用Dart写服务端?
一门语言打天下
这是最大的亮点。你不用在前端用Dart,后端再去学另一门语言。
- 省脑子:不用记不同的语法、构建工具和标准库。
- 开发快:你已经是个Dart老手,写后端逻辑可以直接上手。
- 工具统一:Dart的分析器、格式化工具(
dart format
)、调试器,前后端都能用,爽到飞起。
从界面专家到全栈大牛
自己写后端能让你干更多活儿,从头到尾打造一个完整的应用。
- 独立搞定功能:设计了个新界面需要API?自己动手,分分钟搞定。
- 快速原型:立马搭个模拟服务端或完整API,测试Flutter应用的逻辑,不用等别人。
- 全局视角:你会更深入理解数据流、身份验证、应用扩展的原理。这些知识让你即使只做前端,也能更值钱、更高效。
没你想的那么难
现代Dart服务端框架设计得简单直观。本文会用到的Shelf框架,处理请求的方式简单又强大。只要你会写Dart函数,就能建一个API接口。
从Flutter到服务端:你会的和新学的
从Flutter转到服务端Dart,其实比你想的顺畅。来看看Flutter开发者的视角:
你已经会的(直接拿来用)
- Dart语言:语法、类型安全、
async/await
、集合操作,服务端完全适用。 - 核心库:
dart:core
、dart:convert
(处理JSON)、dart:async
,在服务端也是好帮手。 - Pub生态:你已经知道怎么用
pub.dev
管理pubspec.yaml
里的依赖。 - IDE和工具:VS Code或IntelliJ的Dart插件,写后端代码一样好使。
你可以扩展的知识
- 数据库:用过Flutter的
sqflite
?SQL查询的知识直接能用在后端的PostgreSQL
或MySQL
上。用过Firebase Firestore
?它的文档模型跟MongoDB
很像,Dart后端常用这个。 - 状态管理:Flutter里的Provider、依赖注入等概念,跟服务端管理数据库连接等依赖的逻辑很像。
你需要学的新东西
后端的世界很大,但聚焦下面这些核心内容,就能打好基础,写出强大又安全的应用:
- Web基础(HTTP和REST):得学会HTTP请求/响应的流程,方法(GET、POST、PUT、DELETE)、状态码、头信息。理解RESTful原则是设计干净API的基础。
- 服务端框架(Shelf、Dart Frog):就像Flutter是前端框架,服务端也需要框架来处理请求。Shelf是个简单又灵活的选择,适合入门。
- 路由:得搞懂服务端怎么把请求路径(比如
/users/123
)映射到特定函数。Flutter的路由(像GoRouter
)是导航界面,服务端路由是处理请求,逻辑不太一样。 - 中间件:这是后端核心概念。得学会用中间件处理通用任务,比如记录日志、检查认证令牌、添加标准头信息、捕获错误。
- 认证和授权:得学会验证用户身份(认证)和权限(授权),通常会用到JSON Web Token(JWT)。
- 连接数据库:得学会让服务端连接真实数据库(比如MongoDB或PostgreSQL),永久存储和读取数据。
- 配置和密钥管理:得学会用环境变量管理敏感信息,比如数据库密码、API密钥,绝不能写死在代码里。
- 部署:得学会把服务端应用部署到云平台,让你的Flutter应用和其他客户端能访问。
掌握这些,就能应对大部分后端开发场景。想深入学这些内容,可以看看我写的免费实用指南《Dart & Shelf Backend Handbook》。
实战演练:从按钮到数据库的完整流程
我们来一步步追踪一个请求的完整生命周期,从Flutter的按钮点击到服务端数据库查询,再回到前端。
步骤1:用户点按钮(Flutter界面)
一切从Flutter应用开始。假设有个按钮,点一下就从服务端拉取笔记列表。
// Flutter项目:lib/notes_view.dart
import 'package:flutter/material.dart';
// 假设ApiService在下一步定义
class NotesView extends StatelessWidget {
final _apiService = ApiService();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("服务端笔记")),
body: Center(
child: ElevatedButton(
onPressed: () async {
print('按钮被点!正在拉笔记...');
final notes = await _apiService.fetchNotes();
// 实际应用中会用这些笔记更新界面
print('收到笔记:$notes');
},
child: Text("从服务端拉取笔记"),
),
),
);
}
}
步骤2:发起API调用(Flutter端)
按钮的onPressed
回调会调用一个服务类的方法,用http
包发起网络请求。
// Flutter项目:lib/api_service.dart
import 'package:http/http.dart' as http;
import 'dart:convert';
class ApiService {
final String _baseUrl = "http://localhost:8080"; // 本地服务端地址
Future<List<Map<String, dynamic>>> fetchNotes() async {
try {
final response = await http.get(Uri.parse('$_baseUrl/notes'));
if (response.statusCode == 200) {
// 成功!解析JSON响应
final List<dynamic> data = jsonDecode(response.body);
return data.cast<Map<String, dynamic>>();
} else {
throw Exception('拉取笔记失败:${response.statusCode}');
}
} catch (e) {
throw Exception('拉取笔记出错:$e');
}
}
}
步骤3:服务端处理请求(Shelf)
现在跳到服务端。shelf_router
看到GET /notes
请求,交给对应的处理函数。这个函数调用服务类,组织数据,返回响应。
// 服务端项目:bin/server.dart
import 'dart:convert';
import 'package:shelf/shelf.dart';
import 'package:shelf/shelf_io.dart' as io;
import 'package:shelf_router/shelf_router.dart';
// 假设NoteService在下一步定义
void main() async {
final noteService = NoteService(); // 依赖
final app = Router();
// 路由把/notes路径映射到处理逻辑
app.get('/notes', (Request request) async {
// 1. 调用服务类拉数据
final notes = await noteService.getAllNotes();
// 2. 把Dart的List<Map>编码成JSON字符串
final responseBody = jsonEncode(notes);
// 3. 返回成功的HTTP响应
return Response.ok(responseBody, headers: {
'Content-Type': 'application/json',
});
});
final server = await io.serve(app, 'localhost', 8080);
print('🚀 服务端启动:http://${server.address.host}:${server.port}');
}
步骤4:数据库查询(服务端)
处理函数调用NoteService
获取数据。这个服务类负责实际的数据访问逻辑。这里我们用模拟数据,返回一个List<Map<String, dynamic>>
。
// 服务端项目:lib/note_service.dart
// 服务类封装数据源
class NoteService {
// 模拟的“数据库表”,用List<Map>表示
final List<Map<String, dynamic>> _notes = [
{'id': 1, 'title': '服务端Dart真香'},
{'id': 2, 'title': '跟Flutter共享模型'},
];
Future<List<Map<String, dynamic>>> getAllNotes() async {
// 模拟网络延迟,像真实数据库调用
await Future.delayed(const Duration(milliseconds: 300));
return _notes;
}
}
步骤5:响应和界面更新
- 路由处理函数的
Response.ok(...)
(步骤3)自动生成一个带200 OK
状态和application/json
头的HTTP响应,发送回客户端。 - Flutter的
ApiService
收到响应,fetchNotes
里的print
会在控制台显示JSON字符串。 - 接下来,你可以用
jsonDecode
和喜欢的Flutter状态管理方案(Provider
、Bloc
、Riverpod
等)把数据解析成对象列表,更新界面展示。
你刚用100% Dart完成了一个全栈请求的完整流程!
更优雅的方式:共享模型保类型安全
上面例子中,我们用Map<String, dynamic>
传递数据,凑合能用,但不完美。比如不小心写错键名,notes['titel']
而不是notes['title']
,Dart编译器帮不了你,只能运行时发现问题。
全栈Dart的真正威力在这儿:共享类型安全的模型。
通过在一个共享包(“monorepo”结构)里定义数据类,Flutter和服务器都能用,消灭拼写错误、支持代码补全、建立数据结构的唯一真相。
1. 共享的Note
模型
假设有个packages/models
文件夹,供应用和服务端共享。
// 共享包:packages/models/lib/note.dart
class Note {
final int id;
final String title;
Note({required this.id, required this.title});
// 从JSON创建Note实例
factory Note.fromJson(Map<String, dynamic> json) {
return Note(
id: json['id'] as int,
title: json['title'] as String,
);
}
// 把Note实例转成JSON
Map<String, dynamic> toJson() {
return {
'id': id,
'title': title,
};
}
}
2. 重构服务端
服务端代码变得更简洁、类型安全。
先更新NoteService
,直接用Note
对象。
// 服务端项目:lib/note_service.dart(重构后)
import 'package:models/note.dart'; // 从共享包导入
class NoteService {
Future<List<Note>> getAllNotes() async { // 返回List<Note>
await Future.delayed(const Duration(milliseconds: 200));
return [
Note(id: 1, title: '学服务端Dart'),
Note(id: 2, title: '跟Flutter共享模型!'),
];
}
}
然后,bin/server.dart
用模型的toJson
方法生成响应。
// 服务端项目:bin/server.dart(重构后)
import 'package:models/note.dart';
import 'note_service.dart';
void main() async {
final noteService = NoteService();
final app = Router();
app.get('/notes', (Request request) async {
final notes = await noteService.getAllNotes();
// 把Note对象转成Map再编码
final notesAsMaps = notes.map((note) => note.toJson()).toList();
final jsonResponse = jsonEncode(notesAsMaps);
return Response.ok(
jsonResponse,
headers: {'Content-Type': 'application/json'},
);
});
// ... 启动服务
}
3. 重构Flutter应用
ApiService
也变得类型安全,返回Future<List<Note>>
。
// Flutter项目
import 'package:models/note.dart'; // 从共享包导入
class ApiService {
final String _baseUrl = "http://localhost:8080";
Future<List<Note>> fetchNotes() async { // 返回List<Note>
final response = await http.get(Uri.parse('$_baseUrl/notes'));
if (response.statusCode == 200) {
final List<dynamic> data = jsonDecode(response.body);
// 用fromJson创建Note对象列表
return data.map((json) => Note.fromJson(json)).toList();
} else {
throw Exception('拉取笔记失败');
}
}
}
现在,如果你在Flutter或服务端代码里写错note.titel
,Dart分析器会立刻报错,在运行前就抓住问题。这对代码的健壮性和可维护性是巨大提升。
第一个后端项目:打造简单的To-Do API
要巩固新技能,一个经典的起点是建一个CRUD(创建、读取、更新、删除)API。这是个绝佳的入门项目。
用Shelf和shelf_router
,你可以定义以下接口:
GET /todos
:获取所有待办事项。POST /todos
:用JSON创建新待办。PUT /todos/:id
:更新指定ID的待办。DELETE /todos/:id
:删除指定ID的待办。
这个项目能让你把学到的东西都实践一遍。
想看完整的实现和其他例子,参考《Dart & Shelf Backend Handbook》。
最后一关:用Globe.dev轻松部署
后端建好了,接下来咋办?部署听起来挺吓人,配置服务器、管理容器、搞CI/CD,活儿可不少。
这时候,Globe.dev来救场了。
Globe.dev是为Dart和Flutter生态打造的部署平台,把复杂的事全帮你搞定,部署只要一条命令。
- 零配置:Globe自动识别你的Dart Frog项目。
- 全球边缘网络:你的API全球访问都快。
- 开发者友好:简单的CLI,部署轻松搞定。
- 自动CI/CD:连上GitHub,推送代码,Globe自动构建和部署。
部署一个示例API
来走一遍部署Dart API的流程:
- 拉取示例项目:bash
git clone https://github.com/developerjamiu/todo_backend.git
- 获取依赖:bash
cd todo_backend && dart pub get
- 本地测试:bash(示例用Dart Frog,Shelf应用的部署流程类似。)然后可以用
dart_frog dev
curl
测试接口。 - 用Globe部署:
- 安装CLI:
dart pub global activate globe_cli
- 登录:
globe login
- 部署:
globe deploy
- 安装CLI:
就这么简单,你的API就上线了,全球都能访问。想看更详细的部署教程,包括怎么用curl
测试接口,参考《Deploying a Dart Frog application using Globe》。