こちらは、2025/04/26、京都で行われたFlutterKaigi mini #4 @Kyoto の資料になってます。
使おうと思った理由
AI Agent Hackathon with Google Cloudに参加
条件:AIはGEMIN使え & Googleのクラウドサービスを使え
- GeminiAPIのAPIキーをバックエンドに置こう
- Functionに挑むも、Java、TypeScript、Python依存関係で落ちるorz
→そうだ、Dartでサーバを作ろう(とりあえず、コストは考えない)
DartでWebサーバ
- shelf: 軽量。面倒くさそう(サーバ起動がmain文に入ってるw)Publisher: tetools.dart.dev
- serverpod: 重量(DB, ORMを含む)
- frog: 軽量。わかりやすそう。Publisher: verygood.ventures
作業
-
インストール
dart pub global activate dart_frog_cli
/Users/user_name/.pub-cache/bin/dart_frog にできた -
プロジェクト作成と移動
dart_frog create frog_project_name
cd frog_project_name -
プロジェクト起動
dart_frog dev
確認→ http://localhost:8080/
ディレクトリの中身
README.md
analysis_options.yaml
pubspec.yaml
test
routes/index.dart ← NEW!
index.dart
パス:http:localhost:8080
import 'package:dart_frog/dart_frog.dart';
Response onRequest(RequestContext context) {
return Response(body: 'Welcome to Dart Frog!');
}
個人的におすすめのディレクトリ構成
libを作って、通常のDartクラスのモデルやロジックを作成する
routesの中は、パス処理だけして、細かい処理はlib内のクラスで実施する
ー チャットのデータクラス
mkdir lib
vi lib/chat.dart
class Chat {
const Chat(this.id, this.message);
final int id;
final String message;
Map<String, dynamic> toJson() {
return {
'id': id,
'message': message,
};
}
}
- データベースのクラス
mkdir lib
vi lib/database.dart
import 'dart:async';
import 'package:frog_project_name/chat.dart';
class DataBase {
final _chatList = [
Chat(1, 'one'),
Chat(2, 'two'),
Chat(3, 'three'),
Chat(4, 'four'),
];
Future<Chat?> selectChatById(int id) async =>
_chatList.where((e) => e.id == id).firstOrNull;
Future<List<Chat>> selectAllChat() async => _chatList;
}
- ミドルウェア(システム内の共通クラスを取り扱う)
vi routes/_middleware.dart
import 'package:dart_frog/dart_frog.dart';
import 'package:frog_project_name/database.dart';
final _dataBase = DataBase();
final _databaseProvider = provider<DataBase>((context) => _dataBase);
Handler middleware(Handler handler) {
return handler.use(_databaseProvider);
}
- 一覧取得API(コレクション取得)
http://localhost:8080/chat/
mkdir routes/chat
vi routes/chat/index.dart
import 'dart:convert';
import 'dart:io';
import 'package:dart_frog/dart_frog.dart';
import 'package:frog_project_name/database.dart';
/// curl http://localhost:8080/chat/
Future<Response> onRequest(RequestContext context) async {
return switch (context.request.method) {
HttpMethod.get => _onGet(context),
_ => Future.value(Response(statusCode: HttpStatus.methodNotAllowed)),
};
}
Future<Response> _onGet(RequestContext context) async {
final database = context.read<DataBase>();
final list = await database.selectAllChat();
return Response.json(body: jsonEncode(list));
}
- 詳細取得API(単一リソース取得)
http://localhost:8080/chat/1
vi routes/chat/\[id\].dart
import 'dart:convert';
import 'dart:io';
import 'package:dart_frog/dart_frog.dart';
import 'package:frog_project_name/database.dart';
/// curl http://localhost:8080/chat/1
Future<Response> onRequest(RequestContext context, String id) async {
final intId = int.parse(id);
return switch (context.request.method) {
HttpMethod.get => _onGet(context, intId),
_ => Future.value(Response(statusCode: HttpStatus.methodNotAllowed)),
};
}
Future<Response> _onGet(RequestContext context, int chatId) async {
final database = context.read<DataBase>();
final chat = await database.selectChatById(chatId);
if (chat == null) {
return Response(statusCode: HttpStatus.notFound);
}
return Response.json(body: jsonEncode(chat));
}
- クエリパラメタの取扱
http://localhost:8080/chat?model=gemini2
final modelName = context.request.uri.queryParameters['model'];
デプロイ
-
デプロイ用のWebサーバ用の一式の作成
dart_frog build -
gloudへのデプロイ
gcloud run deploy [SERVICE_NAME] –source build –project=[PROJECT_ID] –region=[REGION] –allow-unauthenticated
(例) gcloud run deploy geminiapi –source build –project=gen-lang-client-080 –region=asia-northeast2 –allow-unauthenticated
サーバの環境変数:Platform.environment をDartソースで使う (String.fromEnvironmentではない)
final key1 = Platform.environment['KEY1'];
感想
gcloudの設定が一番難しかった。インフラ、やりたくねー
routes内のクラスはブレークポイントを使ったデバッグができん(知ってたら教えて)
-
Next
記事がありません