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

## dart_frogについて
#### 1. dart_frogとは
プログラミング言語のDartをバックエンド開発でも使えるようにしたフレームワークです。
Dartといえばクロスプラットフォームアプリ開発ができるFlutterの提供元で、スマホアプリ以外にもWebアプリ、デスクトップアプリなどほとんど全ての開発ができるようになりました。

dart_frogは効率的に、かつ最小限の短い時間でAPIを構築できることを売りとしてるそうです。

以下公式サイト

https://dartfrog.vgv.dev/

#### 2. dart_frog以外にもある
dart_frog以外にもServerPodというものがDartにはあります。
以下公式サイト

https://serverpod.dev/


## 環境構築
#### １. Dartのバージョン確認
`flutter --version`と入力。
現在のFlutterとDartのバージョンが出てきます。
dart_frogを導入するにあたって、Dartのバージョンが`">=2.19.0 <3.0.0"`である必要があります。

#### ２. dart_frogをインストール
・以下のコマンドを入力し、インストールします。
`dart pub global activate dart_frog_cli`

・環境Pathを通す必要があるとのことなので、.zshrcに以下のPathを追記
※bashを使ってる人は.bash_profileに追記
![スクリーンショット 2023-04-16 18.35.13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1812049/437da361-0675-a927-940b-23393e65e6e5.png)
・`source ~/.zshrc`で保存する。

・試しに`dart_frog --version`と入力。
するとバージョンが表示されました。
![スクリーンショット 2023-04-16 18.37.40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1812049/b89ce793-7ac1-4891-f162-c569d14ef5a2.png)

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

#### ３. プロジェクトを作成し、ローカルに起動
・プロジェクト作成
`dart_frog create my_project`
![スクリーンショット 2023-04-16 18.45.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1812049/b85e806a-4a1e-4a17-c508-50912def98f0.png)
作成されました。

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

・`dart_frog dev`でローカルに起動
![スクリーンショット 2023-04-16 18.44.04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1812049/fceacc3e-1a10-1b60-6413-6480eb999bfa.png)
・`localhost:8080`に起動されたとのことなので、アクセスすると「Welcome to Dart Frog!」と表示されてるかと思います。
![スクリーンショット 2023-04-16 18.48.13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1812049/8e044235-93a9-b008-31b7-6ab69f8a0d38.png)

ターミナルのメッセージをみるとdev toolとかdartのvmを確認できるURLも追記されてました。
試しに**dev tool**が起動しているURLに遷移すると問題なく表示されてました。
![スクリーンショット 2023-04-16 18.51.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1812049/5c6969f2-1f3e-c42d-02d1-faa2fcb603c7.png)

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

## 作成されたプロジェクトのディレクトリ構成を確認
![スクリーンショット 2023-04-16 19.32.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1812049/8752eaed-005a-860f-190e-1fb05b619a99.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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1812049/aac57766-c4fc-b978-de39-420e46100c9b.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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1812049/db1e15f6-3806-f2aa-e6ec-6de320550625.png)

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

https://medium.com/@razvantmz/database-for-dart-frog-b21089dac1b2

https://dev.to/ayoubzulfiqar/how-to-create-a-minimal-backend-api-service-using-dart-frog-with-mysql-database-5hig

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

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