dart_frogについて
1. dart_frogとは
プログラミング言語のDartをバックエンド開発でも使えるようにしたフレームワークです。
Dartといえばクロスプラットフォームアプリ開発ができるFlutterの提供元で、スマホアプリ以外にもWebアプリ、デスクトップアプリなどほとんど全ての開発ができるようになりました。
dart_frogは効率的に、かつ最小限の短い時間でAPIを構築できることを売りとしてるそうです。
以下公式サイト
2. dart_frog以外にもある
dart_frog以外にもServerPodというものがDartにはあります。
以下公式サイト
環境構築
1. Dartのバージョン確認
flutter --version
と入力。
現在のFlutterとDartのバージョンが出てきます。
dart_frogを導入するにあたって、Dartのバージョンが">=2.19.0 <3.0.0"
である必要があります。
2. dart_frogをインストール
・以下のコマンドを入力し、インストールします。
dart pub global activate dart_frog_cli
・環境Pathを通す必要があるとのことなので、.zshrcに以下のPathを追記
※bashを使ってる人は.bash_profileに追記
・source ~/.zshrc
で保存する。
・試しにdart_frog --version
と入力。
するとバージョンが表示されました。
無事インストール完了です。
3. プロジェクトを作成し、ローカルに起動
・プロジェクト作成
dart_frog create my_project
作成されました。
・cd my_project
でプロジェクトのフォルダに入る
・dart_frog dev
でローカルに起動
・localhost:8080
に起動されたとのことなので、アクセスすると「Welcome to Dart Frog!」と表示されてるかと思います。
ターミナルのメッセージをみるとdev toolとかdartのvmを確認できるURLも追記されてました。
試しにdev toolが起動しているURLに遷移すると問題なく表示されてました。
・ちなみにbuildするときは以下のコマンドを叩きます。
dart_frog build
作成されたプロジェクトのディレクトリ構成を確認
routesフォルダ内のファイル名が直接エンドポイントになります。
Next.jsのファイルシステムルーティングと同じです。
例えば、このroutesフォルダ内にhello.dartを作成すると、localhost:8080/helloに遷移するとhello.dart内の処理が実行されます。
その他はtestフォルダやyamlファイルなどが入ってて、開発していくうちにpackagesフォルダやlibsフォルダが増える感じかなと思います。
公式ドキュメントを見ながら実際に書いてみる
基本はこのような書き方です。
エンドポイントにリクエストが来た時にonRequestメソッド内の処理が実行されます。
引数のcontextにはRequest情報やミドルウェアの情報が入ってきます。
import 'package:dart_frog/dart_frog.dart';
Response onRequest(RequestContext context) {
return Response(body: 'Welcome to Dart Frog!');
}
1. contextから色々データを取得
・リクエストメソッドを取得
import 'package:dart_frog/dart_frog.dart';
Response onRequest(RequestContext context) {
final request = context.request;
final method = request.method.value;
return Response(body: 'This is a $method request.');
}
・headers情報を取得
import 'package:dart_frog/dart_frog.dart';
Response onRequest(RequestContext context) {
final request = context.request;
final headers = request.headers;
return Response(body: 'Hello World');
}
・クエリパラメータを取得
import 'package:dart_frog/dart_frog.dart';
Response onRequest(RequestContext context) {
final request = context.request;
final params = request.uri.queryParameters;
final name = params['name'] ?? 'no query';
return Response(body: 'Hi $name');
}
nameというクエリパラメータを取得して表示することできました。
2. リクエストボディのデータを取得
import 'package:dart_frog/dart_frog.dart';
Future<Response> onRequest(RequestContext context) async {
final request = context.request;
final body = await request.body();
return Response(body: 'The body is "$body".');
}
・Content-Typeがapplication/json
の場合はこう書く
import 'package:dart_frog/dart_frog.dart';
Future<Response> onRequest(RequestContext context) async {
final request = context.request;
final body = await request.json();
return Response.json(body: {'request_body': body});
}
・Content-Typeがapplication/x-www-form-urlencoded
の場合はこう書く
import 'package:dart_frog/dart_frog.dart';
Future<Response> onRequest(RequestContext context) async {
final request = context.request;
final formData = await request.formData();
return Response.json(body: {'form_data': formData.fields});
}
3. 動的ルーティング
ここもNext.js同様にファイル名を[]
で囲うことで、動的なパラメータにすることができます。
import 'package:dart_frog/dart_frog.dart';
Response onRequest(RequestContext context, String id) {
return Response(body: 'post id: $id');
}
4. MiddleWareの設定
ここでip制限やLogの吐き出し、アクセス権限の確認などを設定できます。
import 'package:dart_frog/dart_frog.dart';
Handler middleware(Handler handler) {
return (context) async {
final response = await handler(context);
return response;
};
}
5. リクエストメソッドごとに条件分けして処理する
switch文などを使ってリクエストメソッドに対しての処理を条件分岐する必要があります。
switch (メソッド) {
case HttpMethod.get:
// TODO: GET メソッド
break;
case HttpMethod.post:
// TODO: POST メソッド
break;
case HttpMethod.put:
// TODO: PUT メソッド
break;
case HttpMethod.delete:
// TODO: DELETE メソッド
break;
}
データベースに接続する方法
ORマッパーがないので結構めんどくさいです。
1. MySQLに接続する処理を作成
MySQLへ接続用のクラスを作成します。
class MySQLClient {
factory MySQLClient() {
return _inst;
}
MySQLClient._internal() {
}
static final MySQLClient _inst = MySQLClient._internal();
MySQLConnection? _connection;
Future<void> _connect() async {
_connection = await MySQLConnection.createConnection(
host: '127.0.0.1',
port: 3306,
userName: '${各々設定}',
password: '${各々設定}',
databaseName: '${各々設定}',
secure: false,
);
await _connection?.connect();
}
Future<IResultSet> execute(
String query, {
Map<String, dynamic>? params,
bool iterable = false,
}) async {
if (_connection == null || _connection?.connected == false) {
await _connect();
}
if (_connection?.connected == false) {
throw Exception('Could not connect to the database');
}
return _connection!.execute(query, params, iterable);
}
}
2. データベースから取得するデータのテーブルモデルを作成
class DatabaseModel {
const DatabaseModel({
this.email,
this.password,
});
factory DatabaseModel.fromJson(Map<String, dynamic> json) {
return DatabaseModel(
email: json['email'] as String,
password: json['password'] as String,
);
}
factory DatabaseModel.fromRowAssoc(Map<String, String?> json) {
return DatabaseModel(
email: json['email'],
password: json['password'],
);
}
Map<String, dynamic> toJson() {
return {
'email': email.toString(),
'password': password.toString(),
};
}
final String? email;
final String? password;
}
3. データベース内のテーブルにアクセスする処理を作成
import 'package:dart_frog_learn/database/model.dart';
import 'package:dart_frog_learn/database/sql_client.dart';
class DataSource {
const DataSource(
this.sqlClient,
);
Future<List<DatabaseModel>> fetchFields() async {
const sqlQuery = 'SELECT email, password FROM users;';
final result = await sqlClient.execute(sqlQuery);
final users = <DatabaseModel>[];
for (final row in result.rows) {
users.add(DatabaseModel.fromRowAssoc(row.assoc()));
}
return users;
}
final MySQLClient sqlClient;
}
4. MiddleWareの設定
import 'package:dart_frog/dart_frog.dart';
import 'package:dart_frog_learn/database/sql_client.dart';
import 'package:dart_frog_learn/source/data_source.dart';
Handler middleware(Handler handler) {
return handler.use(requestLogger()).use(injectionHandler());
}
Middleware injectionHandler() {
return (handler) {
return handler.use(
provider<DataSource>(
(context) => DataSource(context.read<MySQLClient>()),
),
);
};
}
5. エンドポイントの作成
import 'package:dart_frog/dart_frog.dart';
import 'package:dart_frog_learn/source/data_source.dart';
Future<Response> onRequest(RequestContext context) async {
final dataRepository = context.read<DataSource>();
// fetchFieldsメソッド内で`select * from users`が実行されるためDBからデータを取得できる
final users = await dataRepository.fetchFields();
return Response.json(body: users);
}
6.ルートフォルダにmain.dartを用意し、MySQLへの処理を実行させる
import 'dart:io';
import 'package:dart_frog/dart_frog.dart';
import 'package:dart_frog_learn/database/sql_client.dart';
final mysqlClient = MySQLClient();
Future<HttpServer> run(Handler handler, InternetAddress ip, int port) {
return serve(handler.use(databaseHandler()), ip, port);
}
Middleware databaseHandler() {
return (handler) {
return handler.use(
provider<MySQLClient>(
(context) => mysqlClient,
),
);
};
}
7.ブ ラウザに表示されるか確認
以下説明に使わせていただいたサイトです。
触ってみた所感
いいなと思った点
1、Flutter同様にHot Reloadがあるから開発はスムーズ
2、開発ツールもサーバー起動する時に自動で起動してくれるのでありがたい
3、クライアント側をFlutterで開発してたりなど、Dartで統一するのならもってこい
4、Next.js同様にファイル名が直接エンドポイントになるのは直感的で分かりやすい
微妙だと思った点
1、最小限のサイズで手軽にバックエンド側を売りにしてると思うのだが、それだとFastAPIの方がより短く簡単に構築できる
2、ORマッパーがないのでデータベースとの繋ぎ込みが結構大変
3、まだあまり使われないため、参考サイトが全然ない
4、Dartの書き方を結構熟知してないと難しいと思う