🐸

【Flutter】dart_frog でデータ操作からデプロイまで

2025/03/20に公開
1

初めに

今回は Dart Frog の使い方についてまとめてみたいと思います。
基本的な使い方から、Cloud Firestore でのデータ操作、Cloud Run へのデプロイまで各章に分けてみていきます。

記事の対象者

  • Flutter 学習者
  • Dart でバックエンド実装を行いたい方
  • dart_frog でデータ操作、デプロイまで行いたい方

目的

今回の目的は、Dart Frog を用いたデータ操作の方法やデプロイについてざっくり理解することです。
Cloud Firestore でのデータ操作では簡単なCRUD処理を行う方法を理解することを目的とします。
また、デプロイに関しては今回は Cloud Run へのデプロイを行います。

扱わないこと

この記事では以下の項目については詳しく扱わないので、ご注意ください。

  • Dart Frog のテスト
  • dart_frog_auth を用いた認証
  • Cloud Run 以外のサービスへのデプロイ
  • Flutter 側の詳細なUI実装

Dart Frog とは

Dart Frog は Dart のバックエンドフレームワークです。
公式ページ で以下のように説明されています。

A fast, minimalistic backend framework for Dart

Dart 用の高速で最小限のバックエンドフレームワーク

加えて以下のような特徴が紹介されていました。

  • スピードを重視した設計
    • わずか数行で新しいエンドポイントを作成し、ホットリロードで非常に高速に反復処理を実行
  • 軽量
    • シンプルなコアと小さな API サーフェスにより、立ち上げ時間を最小限に抑える
  • Dart
    • Shelf、DevTools、テストなどを備えた強力な Dart エコシステムを活用

導入

以下のパッケージの最新バージョンを pubspec.yamlに記述

pubspec.yaml
dependencies:
  flutter:
    sdk: flutter
  dart_frog: ^1.2.0
  firedart: ^0.9.8
  http: ^1.3.0

または

以下をターミナルで実行

flutter pub add dart_frog firedart http

記事の後半では、Cloud Firestore を用いたデータ操作や Cloud Run へのデプロイを行います。したがって、Firebase のプロジェクトを作成したり、 Cloud Run を有効化する必要があります。
これらの手順については公式ドキュメント等を参照していただければと思います。

実装手順

今回は以下の手順で実装を進めていきます。
1 ~ 6 で Dart Frog の簡単な扱い方を公式ドキュメント をもとに見ていきます。
7, 8 で Cloud Firestore のデータ操作や Cloud Run へのデプロイを扱います。

  1. プロジェクトの作成
  2. 実行方法
  3. 新しいルートの作成
  4. bodyへのアクセス
  5. クエリパラメータへのアクセス
  6. メソッドの種類の判定
  7. Cloud Firestore のデータ操作
  8. Cloud Run へのデプロイ

1. プロジェクトの作成

dart_frog のプロジェクトは以下の2ステップで作成できます。

Dart Frog CLI のインストール
以下のコマンドで Dart Frog CLI をインストールしてアクティベートできます。
これで dart_frog が使用できるようになります。

dart pub global activate dart_frog_cli

プロジェクト作成
dart_frog create コマンドで新たにプロジェクトを作成することができます。

dart_frog create my_project

全く新しいディレクトリとして Dart Frog のプロジェクトを作成した場合は以下のようなディレクトリが作成されるかと思います。

my_project/
├── .dart_frog/
├── .dart_tool/
├── .vscode/
├── routes/
│   ├── index.dart
├── test/
├── .gitignore
├── README.md
├── analysis_options.yaml
├── pubspec.lock
└── pubspec.yaml

Flutter アプリに backend として組み込む場合は以下のようなディレクトリ構造になるかと思います。

dart_frog_sample/
├── .dart_tool/
├── .idea/
├── .vscode/
├── android/
├── backend/   // backend プロジェクトを Dart Frog で作成
│   ├── .dart_frog/
│   ├── .dart_tool/
│   ├── .vscode/
│   ├── routes/
│   ├── test/
│   ├── analysis_options.yaml
│   ├── pubspec.lock
│   ├── pubspec.yaml
│   └── README.md
├── ios/
├── lib/
├── linux/
├── macos/
├── test/
├── web/
├── windows/
├── .gitignore
├── .metadata
├── analysis_options.yaml
├── dart_frog_sample.iml
├── pubspec.lock
├── pubspec.yaml
└── README.md

筆者の手元では後者の既存 Flutter アプリに組み込んだ場合を想定して実装していきます。

2. 実行方法

次に、作成したプロジェクトを開いて実行してみます。
デフォルトで routes/index.dart に以下のようなコードが記載されます。

import 'package:dart_frog/dart_frog.dart';

Response onRequest(RequestContext context) {
  return Response(body: 'Welcome to Dart Frog!');
}

この関数をローカルで実行するには、Dart Frog のルートディレクトリで以下のコマンドを実行します。

dart_frog dev

すると以下のような出力が見つかります。

✓ Running on http://localhost:8080 (78ms)

http://localhost:8080 にブラウザでアクセスするか、ターミナルで以下のコマンドを実行することで、出力結果を確認することができます。

curl --request GET --url http://localhost:8080

ブラウザで確認すると以下のような結果になります。
routes/index.dart のコードの Response に書かれていたテキストが表示されています。

なお、デフォルトのポート番号は 8080 に設定されていますが、/.dart_frog/server.dart の以下の部分を変更するか、実行時に PORT={任意のポート番号} dart_frog dev のように明示的に指定することで変更することができます。

/.dart_frog/server.dart
void main() async {
  final address = InternetAddress.tryParse('') ?? InternetAddress.anyIPv6;
  final port = int.tryParse(Platform.environment['PORT'] ?? '8080') ?? 8080;
  hotReload(() => createServer(address, port));
}

これで実装したコードを実行できるようになりました。

3. 新しいルートの作成

次に新しいルートを作成してみます。
Dart Frog では、File-based routing が採用されています。
Dart Frog で実装されているプロジェクトのディレクトリ構造がそのままルーティングに使用されます。

例えば、routes/hello.dart のようなルートにファイルを作成すると、そのファイルは /hello エンドポイントに対応するようになります。

したがって、routes/posts/[id].dart のように定義すると、 /posts/1, /posts/2 などのパスに対応して動的なルーティングが実装できます。
ただ、今回は動的なルーティングについては扱いません。

以上の点を踏まえて、新しいルートの作成を行います。

新しいルートは以下のコマンドで作成できます。

dart_frog new route {作成するファイルのルート}

Dart Frog のルートディレクトリで以下のコマンドを実行してみると、routes/new_route.dart ファイルが作成されます。

dart_frog new route new_route

作成された routes/new_route.dart ファイルは以下のようになっています。

routes/new_route.dart
import 'package:dart_frog/dart_frog.dart';

Response onRequest(RequestContext context) {
  // TODO: implement route handler
  return Response(body: 'This is a new route!');
}

新しいルートを作成して、 dart_frog dev が実行されている状態で、ブラウザで http://localhost:8080/new_route にアクセスしてみると以下のようになっています。

routes/new_route.dart に記載されている内容が反映されていることがわかります。

4. bodyへのアクセス

次に、リクエストの body にアクセスしてみます。
以下のコマンドで新たにルートを作成します。

dart_frog new route body_access

内容を以下のように修正します。
リクエストの body には context.requestbody() でアクセスできます。

routes/body_access.dart
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".');
}

以下のコマンドを実行して、新たに作成された http://localhost:8080/body_access にアクセスすると「The body is "".」というテキストが表示されているかと思います。
リクエストの body に特にデータを指定していない場合はこのようになります。

curl --request GET --url http://localhost:8080/body_access

エントリーポイントの末尾に --data="Hello World !" を追加して以下のようなコマンドを実行すると、「The body is "Hello World !".」というテキストが表示されるかと思います。

curl --request GET --url http://localhost:8080/body_access --data 'Hello World !'

このように、 context.requestbody() でリクエストの body にアクセスできるようになります。

5. クエリパラメータへのアクセス

次に、リクエストのクエリパラメータにアクセスしてみます。
以下のコマンドで新たにルートを作成します。

dart_frog new route query_params

内容を以下のように修正します。
リクエストのクエリパラメータには context.requesturi.queryParameters でアクセスできます。

routes/query_params.dart
import 'package:dart_frog/dart_frog.dart';

Response onRequest(RequestContext context) {
  final request = context.request;

  final params = request.uri.queryParameters;
  final name = params['name'] ?? 'there';

  return Response(body: 'Hi $name');
}

ファイルの変更が完了したら、 http://localhost:8080/query_params?name=John のように、name パラメータを指定してブラウザを開きます。
すると、「Hi John」というテキストが表示されているかと思います。
このようにしてクエリパラメータにアクセスすることができるようになります。

もちろん、以下のようなコードにすることで、複数のクエリパラメータを渡すこともできます。

routes/query_params.dart
import 'package:dart_frog/dart_frog.dart';

Response onRequest(RequestContext context) {
  final request = context.request;

  final params = request.uri.queryParameters;
  final name = params['name'] ?? 'there';
+ final age = params['age'] ?? 'unknown';

  return Response(body: 'Hi $name, you are $age years old');
}

// http://localhost:8080/query_params?name=John&age=20
// Hi John, you are 20 years old

6. メソッドの種類の判定

次にメソッドの種類を判定してみます。
以下のコマンドで新たにルートを作成します。

dart_frog new route method_access

内容を以下のように修正します。
リクエストのメソッドには context.requestmethod.value でアクセスできます。

routes/query_params.dart
import 'package:dart_frog/dart_frog.dart';

Response onRequest(RequestContext context) {
  final request = context.request;

  final method = request.method.value;
  switch (method) {
    case 'GET':
      return Response(body: 'This is a GET request.');
    case 'POST':
      return Response(body: 'This is a POST request.');
    case _:
      return Response(body: 'This method is not allowed.');
  }
}

ファイルの変更が完了したら、以下のコマンドを実行してみます。

curl --request GET --url http://localhost:8080/method_access   

すると、以下のように GET メソッドであると判定されます。

This is a GET request.

このようにメソッドを判定することで、メソッドに応じた処理を指定することができます。

7. Cloud Firestore のデータ操作

次に Cloud Firestore におけるデータ操作を実装してみたいと思います。
Dart Frog を通して Cloud Firestore のデータ操作を行う場合は firedart パッケージを使用します。

実装

次に新たなルートを作成します。
以下のようなコマンドで、 routes ディレクトリ内で新たに index.dart, _middleware.dart ファイルを生成します。

dart_frog new route /db/firestore
dart_frog new route /db/firestore/_middleware

これで routes/db/firestore ディレクトリ内に、 index.dart, _middleware.dart の二つのファイルが作成されます。

index.dart では、 Cloud Firestore を用いた処理を実装していきます。
_middleware.dart では、 Middleware を作成し、 Cloud Firestore のデータ操作に必要な設定を行います。

_middleware.dart の実装

まずは _middleware.dart の編集を行います。
Middleware では、リクエストが処理される前後に実行する処理を定義することができます。
_middleware.dart という名前で定義されたファイルは同じ階層のリクエストの Middleware として扱われるようになります。

今回は Cloud Firestore にリクエストを送る前に、 Firebase のプロジェクトIDを渡して初期化する処理が必要になります。したがって、その処理を Middleware で行います。

コードは以下の通りです。

routes/db/firestore/_middleware.dart
import 'dart:io';

import 'package:dart_frog/dart_frog.dart';
import 'package:firedart/firedart.dart';

Handler middleware(Handler handler) {
  final projectId = Platform.environment['FIRESTORE_PROJECT_ID'];
  if (projectId == null) {
    throw Exception('FIRESTORE_PROJECT_ID is not set');
  }

  return (context) async {
    if (!Firestore.initialized) {
      Firestore.initialize(projectId);
    }
    final response = await handler(context);
    return response;
  };
}

詳しくみていきます。

以下では、 Firebase のプロジェクトIDを読み取っています。
Platform.environment では環境変数を読み取ることができます。

プロジェクトIDやシークレットキーなどは dart_frog dev でサーバーを起動する段階で事前に環境変数として定義しておくことができます。
もし環境変数として Firebase のプロジェクトIDが定義されていない場合は Exception を投げるようにしています。

dart
Handler middleware(Handler handler) {
  final projectId = Platform.environment['FIRESTORE_PROJECT_ID'];
  if (projectId == null) {
    throw Exception('FIRESTORE_PROJECT_ID is not set');
  }

今回の場合は以下のようなコマンドを実行することで、コード内に Firebase のプロジェクトIDを含まずにアプリを実行することができるようになります。

FIRESTORE_PROJECT_ID={FirebaseのプロジェクトID} dart_frog dev
Firebase のプロジェクトID

Firebase のプロジェクトIDは Firebase Console > プロジェクトの設定 > プロジェクトID で確認することができます。

先程のコードの以下の部分では、 Firestore が初期化されていない場合に限って、環境変数から読み込んだ projectId を渡すことで Firestore の初期化を行なっています。
その後に await handler(context) を記述することで、リクエストを受け取る前の段階で Firestore の初期化を済ませることができます。

routes/db/firestore/_middleware.dart
  return (context) async {
    if (!Firestore.initialized) {
      Firestore.initialize(projectId);
    }
    final response = await handler(context);
    return response;
  };

これで _middleware.dart の実装は完了です。

_middleware.dart の内容
import 'dart:io';

import 'package:dart_frog/dart_frog.dart';
import 'package:firedart/firedart.dart';

Handler middleware(Handler handler) {
  print('middleware is activated');
  final projectId = Platform.environment['FIRESTORE_PROJECT_ID'];
  if (projectId == null) {
    throw Exception('FIRESTORE_PROJECT_ID is not set');
  }

  return (context) async {
    if (!Firestore.initialized) {
      Firestore.initialize(projectId);
    }
    final response = await handler(context);
    return response;
  };
}

index.dart の実装

次に、 Cloud Firestore のデータ操作を含む index.dart の実装を行います。
index.dart の処理は以下の7つに分けてみていきます。

  1. モデルの定義
  2. メソッドの判定
  3. POST処理
  4. GET処理
  5. PUT処理
  6. DELETE処理
  7. その他の処理

まずはモデルの定義を行います。
Dart Frog で使用するモデルは、freezed を用いて作成することができます。しかし、 build_runner ではなく、別の方法で生成する必要があったので、今回は単純なクラスの定義にしています。

コードは以下の通りです。
今回は ID, 名前、メールアドレスを持つ DartFrogUser という簡単なクラスを例にして実装していきます。ID だけは Cloud Firestore 側で生成するため Nullable に設定しています。

routes/db/firestore/index.dart
class DartFrogUser {
  DartFrogUser({
    required this.name,
    required this.email,
    this.id,
  });

  factory DartFrogUser.fromJson(Map<String, dynamic> json) => DartFrogUser(
        id: json['id'] as String?,
        name: json['name'] as String,
        email: json['email'] as String,
      );

  factory DartFrogUser.fromDocument(Document doc) => DartFrogUser(
        id: doc.id,
        name: doc.map['name'] as String,
        email: doc.map['email'] as String,
      );

  final String? id;
  final String name;
  final String email;

  Map<String, dynamic> toJson() => {
        'name': name,
        'email': email,
      };
}

次にメソッドの判定部分についてみていきます。
コードは以下の通りです。
前の章でも扱った通り、メソッドの種類は context.request.method で取得することができます。 method の switch文でそれぞれのメソッドに応じた処理を実装していきます。
また、userCollection として、 Firestore のコレクションを指定しています。このコレクションに対して Firestore のデータ操作を行います。

Future<Response> onRequest(RequestContext context) async {
  final request = context.request;
  final method = request.method;

  final usersCollection = Firestore.instance.collection('users');

  try {
    switch (method) {
      case HttpMethod.post:
        ...
      case HttpMethod.get:
        ...

次にPOST処理についてみていきます。
コードは以下の通りです。

POST処理では、ユーザーの新規作成を行うようにしています。
request.json() as Map<String, dynamic> でリクエストを Map<String, dynamic> 型に変換します。
そしてそれを DartFrogUser.fromJsonDartFrogUser に変換します。

変換したデータを usersCollection.add で指定したコレクションに追加しています。
これで Firestore へのデータの追加が完了します。

データの追加が完了したら、返り値としてドキュメントIDを含む新規ユーザーのデータを返しています。また、この時 201 ステータスコードを返すようにしています。

routes/db/firestore/index.dart
case HttpMethod.post:
  final data = await request.json() as Map<String, dynamic>;
  final user = DartFrogUser.fromJson(data);

  final doc = await usersCollection.add(user.toJson());
  final document = await usersCollection.document(doc.id).get();
  final createdUser = DartFrogUser.fromDocument(document);

  return Response.json(
    body: {
      'id': createdUser.id,
      ...user.toJson(),
    },
    statusCode: 201,
  );

次にGET処理についてみていきます。
コードは以下の通りです。

GET処理はユーザーの全件取得と特定のユーザーの取得の2種類を実装しています。
リクエストのクエリパラメーターに userId が含まれる場合はそのIDに一致するユーザーを取得し、含まれない場合はユーザーの全件取得を行っています。

特定のユーザーを取得する場合は usersCollection.document(userId).get() で取得します。
全件取得する場合は usersCollection.get() で取得します。

routes/db/firestore/index.dart
case HttpMethod.get:
  final queryParams = request.uri.queryParameters;
  final userId = queryParams['id'];

  if (userId != null) {
    try {
      final document = await usersCollection.document(userId).get();
      final user = DartFrogUser.fromDocument(document);

      return Response.json(
        body: {
          'id': user.id,
          ...user.toJson(),
        },
      );
    } catch (e) {
      return Response.json(
        statusCode: 404,
        body: {'error': 'User not found'},
      );
    }
  }

  final documents = await usersCollection.get();
  final users = documents.map((doc) {
    final user = DartFrogUser.fromDocument(doc);
    return {
      'id': user.id,
      ...user.toJson(),
    };
  }).toList();

  return Response.json(body: users);

次にPUT処理についてみていきます。
コードは以下の通りです。

PUT処理では、リクエストのクエリパラメータの含まれる userId と一致するユーザーの更新を行なっています。 userId が存在しない場合や、 userId に一致するユーザーのデータが存在しない場合はステータスコード 400, 404 を返すようにしています。

データの更新処理は docRef.update メソッドで行います。
リクエストの body に含まれるデータを DartFrogUser に変換して渡しています。

routes/db/firestore/index.dart
case HttpMethod.put:
  final userId = request.uri.queryParameters['id'];
  if (userId == null) {
    return Response.json(
      statusCode: 400,
      body: {'error': 'User ID is required'},
    );
  }

  try {
    final docRef = usersCollection.document(userId);
    final doc = await docRef.get();

    if (doc.map.isEmpty) {
      return Response.json(
        statusCode: 404,
        body: {'error': 'User not found with id: $userId'},
      );
    }

    final data = await request.json() as Map<String, dynamic>;
    final user = DartFrogUser.fromJson(data);

    await docRef.update(user.toJson());
    final updatedDoc = await docRef.get();
    final updatedUser = DartFrogUser.fromDocument(updatedDoc);

    return Response.json(
      body: {
        'id': updatedUser.id,
        ...updatedUser.toJson(),
      },
    );
  } catch (e) {
    return Response.json(
      statusCode: 400,
      body: {'error': 'Invalid request data'},
    );
  }

次にDELETE処理についてみていきます。
コードは以下の通りです。

リクエストのクエリパラメータに含まれる userId を取得し、それに合致するユーザーのデータを取得して削除しています。
データの削除は docRef.delete メソッドで実行できます。

routes/db/firestore/index.dart
case HttpMethod.delete:
  final userId = request.uri.queryParameters['id'];
  if (userId == null) {
    return Response.json(
      statusCode: 400,
      body: {'error': 'User ID is required'},
    );
  }

  try {
    final docRef = usersCollection.document(userId);
    final doc = await docRef.get();

    if (doc.map.isEmpty) {
      return Response.json(
        statusCode: 404,
        body: {'error': 'User not found with id: $userId'},
      );
    }

    await docRef.delete();
    return Response.json(
      statusCode: 200,
      body: {'message': 'User deleted successfully'},
    );
  } catch (e) {
    return Response.json(
      statusCode: 500,
      body: {'error': 'Failed to delete user'},
    );
  }

最後にその他の処理についてみていきます。
コードは以下の通りです。

これまでのメソッドに当てはまらない場合はステータスコード405を返すようにしておきます。

routes/db/firestore/index.dart
case _:
  return Response.json(
    statusCode: 405,
    body: {'error': 'Method not allowed'},
  );
index.dart の内容
routes/db/firestore/index.dart
import 'package:dart_frog/dart_frog.dart';
import 'package:firedart/firedart.dart';

class DartFrogUser {
  DartFrogUser({
    required this.name,
    required this.email,
    this.id,
  });

  factory DartFrogUser.fromJson(Map<String, dynamic> json) => DartFrogUser(
        id: json['id'] as String?,
        name: json['name'] as String,
        email: json['email'] as String,
      );

  factory DartFrogUser.fromDocument(Document doc) => DartFrogUser(
        id: doc.id,
        name: doc.map['name'] as String,
        email: doc.map['email'] as String,
      );

  final String? id;
  final String name;
  final String email;

  Map<String, dynamic> toJson() => {
        'name': name,
        'email': email,
      };
}

Future<Response> onRequest(RequestContext context) async {
  final request = context.request;
  final method = request.method;

  final usersCollection = Firestore.instance.collection('users');

  try {
    switch (method) {
      case HttpMethod.post:
        final data = await request.json() as Map<String, dynamic>;
        final user = DartFrogUser.fromJson(data);

        final doc = await usersCollection.add(user.toJson());
        final document = await usersCollection.document(doc.id).get();
        final createdUser = DartFrogUser.fromDocument(document);

        return Response.json(
          body: {
            'id': createdUser.id,
            ...user.toJson(),
          },
          statusCode: 201,
        );

      case HttpMethod.get:
        final queryParams = request.uri.queryParameters;
        final userId = queryParams['id'];

        if (userId != null) {
          try {
            final document = await usersCollection.document(userId).get();
            final user = DartFrogUser.fromDocument(document);

            return Response.json(
              body: {
                'id': user.id,
                ...user.toJson(),
              },
            );
          } catch (e) {
            return Response.json(
              statusCode: 404,
              body: {'error': 'User not found'},
            );
          }
        }

        final documents = await usersCollection.get();
        final users = documents.map((doc) {
          final user = DartFrogUser.fromDocument(doc);
          return {
            'id': user.id,
            ...user.toJson(),
          };
        }).toList();

        return Response.json(body: users);

      case HttpMethod.put:
        final userId = request.uri.queryParameters['id'];
        if (userId == null) {
          return Response.json(
            statusCode: 400,
            body: {'error': 'User ID is required'},
          );
        }

        try {
          final docRef = usersCollection.document(userId);
          final doc = await docRef.get();

          if (doc.map.isEmpty) {
            return Response.json(
              statusCode: 404,
              body: {'error': 'User not found with id: $userId'},
            );
          }

          final data = await request.json() as Map<String, dynamic>;
          final user = DartFrogUser.fromJson(data);

          await docRef.update(user.toJson());
          final updatedDoc = await docRef.get();
          final updatedUser = DartFrogUser.fromDocument(updatedDoc);

          return Response.json(
            body: {
              'id': updatedUser.id,
              ...updatedUser.toJson(),
            },
          );
        } catch (e) {
          return Response.json(
            statusCode: 400,
            body: {'error': 'Invalid request data'},
          );
        }

      case HttpMethod.delete:
        final userId = request.uri.queryParameters['id'];
        if (userId == null) {
          return Response.json(
            statusCode: 400,
            body: {'error': 'User ID is required'},
          );
        }

        try {
          final docRef = usersCollection.document(userId);
          final doc = await docRef.get();

          if (doc.map.isEmpty) {
            return Response.json(
              statusCode: 404,
              body: {'error': 'User not found with id: $userId'},
            );
          }

          await docRef.delete();
          return Response.json(
            statusCode: 200,
            body: {'message': 'User deleted successfully'},
          );
        } catch (e) {
          return Response.json(
            statusCode: 500,
            body: {'error': 'Failed to delete user'},
          );
        }

      case _:
        return Response.json(
          statusCode: 405,
          body: {'error': 'Method not allowed'},
        );
    }
  } catch (e) {
    return Response.json(
      statusCode: 500,
      body: {'error': e.toString()},
    );
  }
}

これで Cloud Firestore のデータ操作の実装は完了です。

8. Cloud Run へのデプロイ

最後に Cloud Run へのデプロイまで行いたいと思います。
Cloud Run へのデプロイは Dart Frog の Deploy > Google Cloud Run のドキュメントをもとに行います。

デプロイは以下の手順で行います。
なお、すでに gcloud コマンドが使用できる場合はステップ2から進められます。

  1. gcloud コマンドのインストール、ログイン
  2. ビルド
  3. デプロイ

1. gcloud コマンドのインストール、ログイン

gcloud コマンドがインストールされていない場合は gcloud CLI をインストールする のドキュメントを見ながらインストールしていきます。

基本的にはドキュメントに沿って進めていくと gcloud コマンドが使用できるようになります。
なお、自分の手元では command not found になったため、こちらの記事を参考にさせていただいて解消できました。

gcloud コマンドがインストールできたらログインを実行します。
ターミナルで以下のコマンドを実行して、 gcloud login を行います。

gcloud auth login

コマンドを実行すると以下のようにログインを求められるので、ログインしておきます。

2. ビルド

次に Dart Frog を用いた実装を行なったディレクトリで以下のコマンドを実行します。

dart_frog build

これでルートディレクトリに build ディレクトリが作成されます。
この build ディレクトリ配下のファイルをもとにしてデプロイを行います。

3. デプロイ

build ディレクトリが作成されたら最後にデプロイを行います。
以下のコマンドでデプロイができます。

gcloud run deploy [SERVICE_NAME] \
  --source build \
  --project=[PROJECT_ID] \
  --region=[REGION] \
  --allow-unauthenticated

SERVICE_NAME はデプロイしたい関数の名前を指定しておきます。
source では build ディレクトリを指定しておくことで、build ディレクトリの内容をもとにデプロイを行うことができます。
PROJECT_ID は Cloud Firestore のデータ操作を行なった際に指定したIDと同じで、Firebase Console > プロジェクトの設定 > プロジェクトIDで確認することができます。
REGION はデプロイを行うリージョンを指定します。東京の場合は asia-northeast1 となります。それぞれのリージョンはこちらで参照可能です。
allow-unauthenticated の指定をしておくことで、認証していないユーザーからのアクセスも受け入れるように設定することができます。

例えば以下のコマンドでは、 user-management123 というプロジェクトIDのプロジェクトにおいて、東京リージョンで users という名前のサービスをデプロイすることができます。

gcloud run deploy users \
  --source build \
  --project=user-management123 \
  --region=asia-northeast1 \
  --allow-unauthenticated

デプロイが完了してから少し時間をおいて、 Cloud Run を確認してみるとデプロイされたサービスを確認できるかと思います。
あとは作成された Cloud Run のエンドポイントを指定して叩けば Dart Frog で書かれた処理が実行されるかと思います。

以上です。

まとめ

最後まで読んでいただいてありがとうございました。

今回は Dart Frog を用いた基本的な実装や Cloud Firestore のデータ操作、デプロイを行う方法についてまとめました。
やはりフロントエンドとバックエンドで言語を統一して実装できるので、実装やしやすいように感じました。

誤っている点やもっと良い書き方があればご指摘いただければ幸いです。

参考

https://dartfrog.vgv.dev/

https://dartfrog.vgv.dev/docs/overview

https://cloud.google.com/sdk/docs/install?hl=ja

https://qiita.com/Y-Fujikawa/items/2c468eb85f3dec52c374

1

Discussion