4
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

バックエンド側もDartで開発できる、dart_frogを紹介

Last updated at Posted at 2023-04-16

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に追記
スクリーンショット 2023-04-16 18.35.13.png
source ~/.zshrcで保存する。

・試しにdart_frog --versionと入力。
するとバージョンが表示されました。
スクリーンショット 2023-04-16 18.37.40.png

無事インストール完了です。

3. プロジェクトを作成し、ローカルに起動

・プロジェクト作成
dart_frog create my_project
スクリーンショット 2023-04-16 18.45.37.png
作成されました。

cd my_projectでプロジェクトのフォルダに入る

dart_frog devでローカルに起動
スクリーンショット 2023-04-16 18.44.04.png
localhost:8080に起動されたとのことなので、アクセスすると「Welcome to Dart Frog!」と表示されてるかと思います。
スクリーンショット 2023-04-16 18.48.13.png

ターミナルのメッセージをみるとdev toolとかdartのvmを確認できるURLも追記されてました。
試しにdev toolが起動しているURLに遷移すると問題なく表示されてました。
スクリーンショット 2023-04-16 18.51.43.png

・ちなみにbuildするときは以下のコマンドを叩きます。
dart_frog build

作成されたプロジェクトのディレクトリ構成を確認

スクリーンショット 2023-04-16 19.32.05.png
確かにプロジェクト内のファイル数はかなり少ないです。

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というクエリパラメータを取得して表示することできました。
スクリーンショット 2023-04-16 21.15.19.png
 

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.ブ ラウザに表示されるか確認

ユーザー一覧テーブルのデータが無事取得できました。
スクリーンショット 2023-04-16 23.39.27.png

以下説明に使わせていただいたサイトです。

触ってみた所感

いいなと思った点
1、Flutter同様にHot Reloadがあるから開発はスムーズ
2、開発ツールもサーバー起動する時に自動で起動してくれるのでありがたい
3、クライアント側をFlutterで開発してたりなど、Dartで統一するのならもってこい
4、Next.js同様にファイル名が直接エンドポイントになるのは直感的で分かりやすい
 

微妙だと思った点
1、最小限のサイズで手軽にバックエンド側を売りにしてると思うのだが、それだとFastAPIの方がより短く簡単に構築できる
2、ORマッパーがないのでデータベースとの繋ぎ込みが結構大変
3、まだあまり使われないため、参考サイトが全然ない
4、Dartの書き方を結構熟知してないと難しいと思う

4
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?