Dart на сервере: Руководство для инженера Flutter
FlutterPulseЭта статья переведена специально для канала FlutterPulse. В этом канале вы найдёте много интересных вещей, связанных с Flutter. Не забывайте подписываться! 🚀

Как разработчик Flutter, вы освоили искусство создания красивых, высокопроизводительных пользовательских интерфейсов. Вы живете и дышите виджетами…
Как разработчик Flutter, вы освоили искусство создания красивых, высокопроизводительных пользовательских интерфейсов. Вы живете и дышите виджетами, состоянием и языком Dart. Но что происходит, когда вам нужно получить данные, аутентифицировать пользователя или запустить некоторую логику, которая не принадлежит клиенту?
Традиционно, это означало передачу работы специальной бэкенд-команде или болезненное изучение нового языка и фреймворка, такого как Node.js, Python или Go.
Но что, если бы вы могли создать весь приложение, от кнопки в вашем приложении до запроса базы данных на сервере, используя один язык, который вы уже знаете и любите?
Добро пожаловать в мир серверной части Dart. Это руководство покажет вам, почему вам следует заботиться, как это работает и как вы можете начать сегодня.
Для обзора экосистемы Dart с полным стеком, посмотрите мою статью Dart на сервере: разблокировка истинного потенциала полного стека"
"Почему": superheroes ваши навыки с серверной частью Dart
Давайте перейдем сразу к делу. Почему вы, занятый инженер Flutter, должны инвестировать время в изучение написания Dart на сервере?
Один язык, чтобы править всем
Это самое значительное преимущество. Вам не нужно переключаться между контекстами между Dart на фронте и другим языком на бэкенде.
- Меньшая когнитивная нагрузка: Нет больше запоминания разных синтаксисов, инструментов сборки и стандартных библиотек.
- Быстрая разработка: Вы уже профессионал в Dart. Вы можете начать строить логику бэкенда сразу.
- Последовательные инструменты: Используйте один и тот же фантастический анализатор Dart, форматировщик (
dart format), и отладчик на всем вашем стеке.
От специалиста по UI до архитектора полного стека
Написание своего бэкенда наделяет вас возможностью делать больше и строить полные приложения от начала до конца.
- Строить функции независимо: Вам нужен новый конечный пункт API для того экрана, который вы только что спроектировали? Вы можете его создать сами.
- Прототипировать быстрее: Быстро создайте макет сервера или полный API, чтобы протестировать функциональность вашего приложения Flutter, не дожидаясь кого-либо.
- Понимать полную картину: Вы получите глубокое понимание того, как течет информация, как работает аутентификация и что делает приложение масштабируемым. Эти знания делают вас более ценными и эффективными разработчиками, даже когда вы работаете только на фронте.
Это проще, чем вы думаете
Современные фреймворки серверной части Dart предназначены для того, чтобы быть минимальными и интуитивными. Фреймворк Shelf, который мы будем использовать в этом руководстве, предоставляет простой и мощный способ обработки запросов. Если вы можете написать функцию в Dart, вы можете построить конечную точку API.
Переход: что вы знаете, и что вам нужно узнать
Переход от Flutter к серверной части Dart более естественен, чем вы думаете. Вот быстрый обзор с точки зрения разработчика Flutter:
Что вы уже знаете (и можете повторно использовать)
- Язык Dart: Все ваши знания синтаксиса, типовой безопасности,
async/await, и коллекций применяются напрямую. - Основные библиотеки:
dart:core,dart:convert(для JSON), иdart:asyncявляются вашими лучшими друзьями на сервере тоже. - Экосистема Pub: Вы уже знаете, как использовать
pub.devи управлять зависимостями вpubspec.yaml. - IDE & Инструменты: VS Code или IntelliJ с плагином Dart работают идеально для разработки бэкенда.
Знания, которые можно расширить
- Базы данных: Вы использовали
sqfliteв Flutter? Знания SQL-запросов напрямую применимы к серверным реляционным базам данных, таким какPostgreSQLилиMySQL. ИспользовалиFirebase Firestore? Вы обнаружите, что его модель, основанная на документах, очень похожа наMongoDB, популярный выбор для серверов Dart. - Концепции управления состоянием: Концепции провайдеров и внедрения зависимостей в Flutter напрямую аналогичны тому, как вы будете управлять зависимостями (например, соединениями с базой данных) на сервере.
Что вам нужно изучить: Основы
Хотя мир бэкенда огромен, фокусировка на этих основах даст вам прочный фундамент для создания мощных и безопасных приложений.
- Основные веб-концепции (HTTP и REST): Вам нужно будет изучить цикл запроса/ответа HTTP, методы (GET, POST, PUT, DELETE), коды состояния и заголовки. Понимание принципов REST является основой для проектирования чистых API.
- Обработка запросов с помощью серверного фреймворка (Shelf, Dart Frog): Как и Flutter предоставляет фреймворк для UI, вам понадобится серверный фреймворк для обработки веб-запросов. Отличным местом для начала является Shelf, минимальный и составной фреймворк для Dart.
- Маршрутизация: Вам нужно будет понять, как сервер сопоставляет входящие пути/точки запроса (например,
/users/123) с определенными функциями. Хотя вы знакомы с маршрутизацией в Flutter (какGoRouter), маршрутизация на сервере отличается и не涉ивает навигацию по экранам UI. - Мидлвэр: Это является ключевой концепцией бэкенда. Вам нужно будет изучить, как использовать мидлвэр для выполнения задач, которые применяются к многим запросам, таких как ведение журнала, проверка токенов аутентификации, добавление стандартных заголовков и перехват ошибок.
- Аутентификация и авторизация: Вам нужно будет изучить, как проверить личность пользователя (Аутентификация) и определить, что он может делать (Авторизация). Это часто включает в себя методы, такие как JSON Web Tokens (JWT).
- Подключение к базе данных: Вам нужно будет изучить, как подключить ваш сервер к реальной базе данных (например, MongoDB или PostgreSQL), чтобы хранить и извлекать данные постоянно.
- Управление конфигурацией и секретами: Вам нужно будет изучить, как управлять конфиденциальной информацией, такой как пароли баз данных и ключи API, с помощью переменных среды, чтобы они никогда не были закодированы в вашем исходном коде.
- Развертывание: Вам нужно будет изучить, как взять ваше серверное приложение и запустить его на облачной платформе, чтобы оно было доступно вашему приложению Flutter и другим клиентам в Интернете.
Освоение этих тем обеспечит вам способность справляться с большинством распространенных сценариев разработки бэкенда. Лучшее место для изучения всех этих концепций в деталях - это Руководство по бэкенду Dart и Shelf, бесплатное, практическое руководство, которое я создал специально для этой цели.
"Как": пошаговый обзор кода
Давайте проанализируем весь жизненный цикл запроса с кодом, от кнопки Flutter до базы данных и обратно.
Шаг 1: Пользователь нажимает кнопку (UI 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();
// В реальном приложении вы бы использовали эти заметки для обновления вашего UI
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 в строку 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: Запрос базы данных (Dart Server)
Обработчик вызывает NoteService для получения данных. Этот сервис содержит фактическую логику доступа к данным. Для этого примера мы выделим его, чтобы вернуть простой List<Map<String, dynamic>>.
// В вашем серверном проекте, например, в lib/note_service.dart
// Этот класс сервиса абстрагирует источник данных
class NoteService {
// Фальшивая "таблица базы данных" заметок, представленная как список карт
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: Ответ и обновление UI
- Метод
Response.ok(...)в нашем обработчике маршрута (Шаг 3) автоматически создает правильный HTTP-ответ с статусом200 OKи заголовкомapplication/json, и отправляет его обратно клиенту. - Наш сервис API Flutter
ApiServiceполучает этот ответ. ИнструкцияprintвfetchNotesпокажет строку JSON в консоли вашего приложения Flutter. - Оттуда вы можете использовать
jsonDecodeи ваше любимое решение для управления состоянием (Provider,Bloc,Riverpod, и т.д.) для разбора данных в список объектов и перестроения UI для их отображения.
Вы только что отследили полный запрос от начала до конца, используя 100% Dart!
Лучший способ: Общие модели для типобезопасности
В приведенном выше коде вы могли заметить, что мы передавали Map<String, dynamic> туда и обратно. Это работает, но не идеально. Что если вы допустите ошибку в имени ключа, как notes['titel'] вместо notes['title']? Компилятор Dart не сможет вам помочь, и вы обнаружите ошибку только во время выполнения.
Именно здесь проявляется истинная сила полноценного Dart: типобезопасные модели.
Определяя класс данных один раз в общем пакете (структура "монорепозиторий"), ваше приложение 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});
// Создание экземпляра Note из JSON-карты
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('Ошибка при загрузке заметок');
}
}
}
Теперь, если вы попытаетесь получить доступ к note.titel где-либо в вашем коде Flutter или сервере, анализатор Dart сразу же выдаст ошибку, обнаруживая ошибку до запуска. Это значительное улучшение в плане надежности и поддержки.
Создание своего первого бэкэнда: простой API для заметок
Классическая отправная точка для укрепления ваших новых навыков - это создание простого API CRUD (Create, Read, Update, Delete). Это отличный первый проект для решения после прочтения этого руководства.
Используя Shelf и shelf_router, вы бы определили обработчики для следующих конечных точек:
GET /todos: Извлекает все заметки.POST /todos: Создает новую заметку из JSON-тела.PUT /todos/:id: Обновляет конкретную заметку по ее ID.DELETE /todos/:id: Удаляет конкретную заметку по ее ID.
Этот проект даст вам практический опыт работы со всем, что вы узнали.
Для полной реализации этого API и многое другое, обратитесь к Руководству по бэкэнду Dart & Shelf
Окончательное препятствие: развертывание, упрощенное с помощью Globe.dev
Хорошо, вы создали свой бэкэнд. Теперь что? Развертывание может быть пугающим. Настройка серверов, управление контейнерами и настройка CI/CD-пайплайнов - это работа сама по себе.
Именно здесь приходит на помощь Globe.dev.
Globe.dev - это платформа развертывания специально созданная для экосистемы Dart и Flutter. Они обрабатывают всю сложность за вас, делая развертывание простым процессом, который можно выполнить с помощью одной команды.
- Нулевая конфигурация: Globe автоматически обнаруживает, что вы развертываете проект Dart Frog.
- Глобальная сеть ребер: Ваш API будет быстрым для пользователей по всему миру.
- Интуитивный и дружественный для разработчиков: С простым CLI, Globe делает развертывание легким.
- Бесшовная CI/CD: Просто подключите свою учетную запись GitHub и пусть. Globe автоматически соберет и развернет ваш сервер.
Развертывание примера API
Давайте пройдемся по развертыванию примера API Dart.
- Получите пример проекта:
git clonehttps://github.com/developerjamiu/todo_backend.git - Загрузите зависимости:
cd todo_backend && dart pub get - Тестируйте локально:
dart_frog dev(Этот пример использует Dart Frog, но процесс развертывания аналогичен для приложений Shelf). Затем вы можете протестировать конечные точки с помощьюcurl. - Разверните с помощью Globe:
- Установите CLI:
dart pub global activate globe_cli - Войдите:
globe login - Разверните:
globe deploy
Как только вы это сделаете, ваш API будет доступен и доступен для всех в интернете. Для более подробного описания этого процесса развертывания, включая то, как протестировать каждую конечную точку с помощью curl, ознакомьтесь с этим руководством: Развертывание приложения Dart Frog с помощью Globe.
Заключение: ваше путешествие в полный стек начинается сейчас
Для разработчика Flutter самым большим препятствием на пути к изучению разработки бэкэнда часто является новый язык и инструменты. С серверным Dart это барьер устранен. У вас уже есть самый важный навык.
Путь вперед заключается в применении того, что вы знаете, к новому контексту; понимании веб-запросов, обработке их с помощью фреймворка, такого как Shelf, и подключении к базе данных. Это логическое расширение ваших текущих знаний, а не полный перезапуск.
Лучший способ начать - это построить что-то. Возьмите концепции из этого руководства, используйте справочник как справочник, и создайте свой первый простой API. Вы, скорее всего, будете удивлены, насколько это кажется естественным и насколько мощным является контроль над приложением с помощью одного языка.