Skip to content

用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:coredart:convert(处理JSON)、dart:async,在服务端也是好帮手。
  • Pub生态:你已经知道怎么用pub.dev管理pubspec.yaml里的依赖。
  • IDE和工具:VS Code或IntelliJ的Dart插件,写后端代码一样好使。

你可以扩展的知识

  • 数据库:用过Flutter的sqflite?SQL查询的知识直接能用在后端的PostgreSQLMySQL上。用过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应用开始。假设有个按钮,点一下就从服务端拉取笔记列表。

dart
// 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包发起网络请求。

dart
// 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请求,交给对应的处理函数。这个函数调用服务类,组织数据,返回响应。

dart
// 服务端项目: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>>

dart
// 服务端项目: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状态管理方案(ProviderBlocRiverpod等)把数据解析成对象列表,更新界面展示。

你刚用100% Dart完成了一个全栈请求的完整流程!

更优雅的方式:共享模型保类型安全

上面例子中,我们用Map<String, dynamic>传递数据,凑合能用,但不完美。比如不小心写错键名,notes['titel']而不是notes['title'],Dart编译器帮不了你,只能运行时发现问题。

全栈Dart的真正威力在这儿:共享类型安全的模型

通过在一个共享包(“monorepo”结构)里定义数据类,Flutter和服务器都能用,消灭拼写错误、支持代码补全、建立数据结构的唯一真相。

1. 共享的Note模型

假设有个packages/models文件夹,供应用和服务端共享。

dart
// 共享包: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对象。

dart
// 服务端项目: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方法生成响应。

dart
// 服务端项目: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>>

dart
// 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的流程:

  1. 拉取示例项目
    bash
    git clone https://github.com/developerjamiu/todo_backend.git
  2. 获取依赖
    bash
    cd todo_backend && dart pub get
  3. 本地测试
    bash
    dart_frog dev
    (示例用Dart Frog,Shelf应用的部署流程类似。)然后可以用curl测试接口。
  4. 用Globe部署
    • 安装CLI:dart pub global activate globe_cli
    • 登录:globe login
    • 部署:globe deploy

就这么简单,你的API就上线了,全球都能访问。想看更详细的部署教程,包括怎么用curl测试接口,参考《Deploying a Dart Frog application using Globe》。