iimon TECH BLOG

iimonエンジニアが得られた経験や知識を共有して世の中をイイモンにしていくためのブログです

Firestore REST APIをTypeScriptで使ってみた

こんにちは。バックエンドエンジニアの木暮です。

今回はfirestoreのREST APIを使う機会がありましたので TypeScriptでの使用方法を紹介させていただきます!
私の読解力の低さのせいだとは思いますが、なかなか公式ドキュメントを読んでもすんなりと動かすことができませんでした。
なので備忘録も兼ねてこちらを書かせていただきました!初めて使う方の手助けになれば幸いです!

下準備

まず、REST APIを使用するにあたってjwtトークンを取得する必要があります。

jwtトークンは以下のメソッドを使用して取得します。 https://firebase.google.com/docs/reference/js/v8/firebase.User#getidtoken

以下にログインユーザのjwtトークンを取得する例を示します。

const getJwt = async (): Promise<string> => {

  // firebaseを使用するための下準備
  const app = initializeApp({
    apiKey: "dummyApiKey",
    projectId: "dummyProjectId",
  });

  // firebaseログイン情報
  const email = "dummy@example.com";
  const password = "dummyPassword";

  // firebaseにログイン
  await signInWithEmailAndPassword(getAuth(app), email, password);

  // Authオブジェクトを取得
  const auth = getAuth(app);

  // JWTトークンを取得
  const jwt = await auth.currentUser?.getIdToken();

  if (!jwt) {
    throw new Error("Failed to get JWT token.");
  }

  return jwt;
};

上記のメソッドで取得したトークン文字列はBearer認証で使用します。

ドキュメントの作成

fetchを使用してのドキュメントの作成を実行します。

Method: projects.databases.documents.createDocument  |  Firebase

段階を踏んで解説していきます。 まずは最初に定義する変数について解説です。

  const jwt = await getJwt();
  const collectionName = "hogeCollection";
  const projectId = "dummyProjectId";
  const requestUrl = `https://firestore.googleapis.com/v1/projects/${projectId}/databases/(default)/documents/${collectionName}`;

collectionNameにはドキュメントの書き込み先となるコレクションの名前を指定してください。
projectIdには自身のプロジェクトのIDを指定します。
requestUrlではcollectionNameprojectIdを使ってリクエスト先URLを組み立てています。

次にリクエストボディ部分の解説です。

REST Resource: projects.databases.documents  |  Firebase

上記ドキュメント内のfieldsに沿ってボディを設定します。

  "fields": {
    string: {
      object (Value)
    },
    ...
  },

この形でリクエストのボディを設定して設定値をJSON.stringifyを使用JSON文字列として設定します。

object (Value)に設定できる値は以下のドキュメントに書かれています。

Value  |  Firestore  |  Google Cloud

よく使う設定値をピックアップして解説します。

   
定義設定する値
nullValue nullを設定 null
booleanValue bool値を設定 true
integerValue 整数値を設定 "123"
doubleValue 倍精度浮動小数点数型を設定 123.456
timestampValue ISO 8601フォーマットでtimestampを設定 "2014-10-02T15:01:23Z"
stringValue 文字列を設定 "Hello World"
arrayValue 配列を設定 サンプルコード参照
mapValue mapを設定 サンプルコード参照

そのほかの設定できる値や詳細は上記のリンクを参照してください。

実際にobject (Value)を設定する際は以下のような形式で設定を行います。

{ドキュメントの項目名}: 
  {値の型}:{値},
}

具体的なリクエストボディは以下のようになります。

  const requestBody = JSON.stringify({
    fields: {
      null: {
        nullValue: null,
      },
      bool: {
        booleanValue: true,
      },
      int: {
        integerValue: "123",
      },
      double: {
        doubleValue: 123.456,
      },
      timestamp: {
        timestampValue: new Date().toISOString(),
      },
      string: {
        stringValue: "Hello World",
      },
      array: {
        arrayValue: {
          values: [
            {
              stringValue: "Hello",
            },
            {
              stringValue: "World",
            },
          ],
        },
      },
      map: {
        mapValue: {
          fields: {
            hello: {
              stringValue: "Hello",
            },
            world: {
              stringValue: "World",
            },
          },
        },
      },
    },
  });

最後にリクエストヘッダーの設定についてです。

  const fetchOptions = {
    method: "POST",
    headers: {
      Authorization: `Bearer ${jwt}`,
      "Content-Type": "application/json",
    },
    body: requestBody,
  };

headersAuthorizationに冒頭で取得しているトークン文字列を設定します。 HTTPリクエストメソッドはPOSTになります。

全体では以下のような実装になります。

const store = async () => {
  const jwt = await getJwt();
  const collectionName = "hogeCollection";
  const requestUrl = `https://firestore.googleapis.com/v1/projects/${projectId}/databases/(default)/documents/${collectionName}`;

  const requestBody = JSON.stringify({
    fields: {
      null: {
        nullValue: null,
      },
      bool: {
        booleanValue: true,
      },
      int: {
        integerValue: "123",
      },
      double: {
        doubleValue: 123.456,
      },
      timestamp: {
        timestampValue: new Date().toISOString(),
      },
      string: {
        stringValue: "Hello World",
      },
      array: {
        arrayValue: {
          values: [
            {
              stringValue: "Hello",
            },
            {
              stringValue: "World",
            },
          ],
        },
      },
      map: {
        mapValue: {
          fields: {
            hello: {
              stringValue: "Hello",
            },
            world: {
              stringValue: "World",
            },
          },
        },
      },
    },
  });

  const fetchOptions = {
    method: "POST",
    headers: {
      Authorization: `Bearer ${jwt}`,
      "Content-Type": "application/json",
    },
    body: requestBody,
  };

  await fetch(requestUrl, fetchOptions);
};

上記メソッドを実行後にfirestoreコンソールからすると登録できていることが確認できました

登録時のドキュメント名を指定したい場合はリクエストURLに?documentId={ドキュメント名}の形でクエリパラメータを追加します。 以下のURLでリクエストを送ると指定したドキュメント名で登録されていることが確認できます。

const requestUrl = `https://firestore.googleapis.com/v1/projects/${projectId}/databases/(default)/documents/${collectionName}?documentId=hoge` 

ドキュメントの更新

Method: projects.databases.documents.patch  |  Firebase

ドキュメントの更新はrequestURLを以下のように指定します

  const requestUrl = `https://firestore.googleapis.com/v1/projects/${projectId}/databases/(default)/documents/${documentPath}`;

HTTPリクエストメソッドにはPATCHを指定します。

  const fetchOptions = {
    method: "PATCH",
    headers: {
      Authorization: `Bearer ${jwt}`,
      "Content-Type": "application/json",
    },
    body: requestBody,
  };

全体的な実装は以下のようになります。
以下の例はhogeCollectionのhogeドキュメントをnullで更新しています。

const update = async () => {
  const jwt = await getJwt();
  const collectionName = "hogeCollection";
  const requestUrl = `https://firestore.googleapis.com/v1/projects/${projectId}/databases/(default)/documents/${collectionName}/hoge`;

  const requestBody = JSON.stringify({
    fields: {
      null: {
        nullValue: null,
      },
    },
  });

  const fetchOptions = {
    method: "PATCH",
    headers: {
      Authorization: `Bearer ${jwt}`,
      "Content-Type": "application/json",
    },
    body: requestBody,
  };

  await fetch(requestUrl, fetchOptions);
};

上記メソッドを実行後の状態は以下のようになります

ドキュメントの取得

ドキュメントの単一取得を説明します。

Method: projects.databases.documents.get  |  Firebase

サンプルコードはこのようになります

const get = async () => {
  const jwt = await getJwt();
  const collectionName = "hogeCollection";
  const requestUrl = `https://firestore.googleapis.com/v1/projects/${projectId}/databases/(default)/documents/${collectionName}/hoge`;

  const fetchOptions = {
    method: "GET",
    headers: {
      Authorization: `Bearer ${jwt}`,
      "Content-Type": "application/json",
    },
  };

  const res = await fetch(requestUrl, fetchOptions);
  const data = await res.json();
  console.log(data);
};

リクエスト先は更新時と同様の指定方法になります。 そして、HTTPリクエストメソッドはGETになります。 実行してレスポンスの確認すると以下のようになっています。

{
  name: 'projects/dummy-project-id/databases/(default)/documents/hogeCollection/hoge',
  fields: { null: { nullValue: null } },
  createTime: '2024-09-22T04:28:13.256088Z',
  updateTime: '2024-09-22T04:36:37.323123Z'
}

nameキーには取得先が設定されています。
fieldsキーには取得したドキュメントの値が設定されています。今回はnullで更新後のドキュメントを取得しているので簡単な値しか取れていません。
そして、createTimeにはドキュメントの作成日時、updateTimeにはドキュメントの更新日時が設定されます。

ドキュメントの削除

最後にドキュメントの削除を紹介します。

Method: projects.databases.documents.delete  |  Firebase

deleteもリクエスト先は更新、取得同様になります。 違いはHTTPリクエストメソッドがDELETEになる点のみです。

const _delete = async () => {
  const jwt = await getJwt();
  const collectionName = "hogeCollection";
  const requestUrl = `https://firestore.googleapis.com/v1/projects/${projectId}/databases/(default)/documents/${collectionName}/hoge`;

  const fetchOptions = {
    method: "DELETE",
    headers: {
      Authorization: `Bearer ${jwt}`,
      "Content-Type": "application/json",
    },
  };

  await fetch(requestUrl, fetchOptions);
};

実行後、firestoreコンソールから該当のドキュメントが削除されたことが確認できます。

最後に

ほんの一部にはなりますが、firestoreのREST APIを通してのドキュメントの作成、更新、取得、削除の紹介をさせていただきました。 他にもAPIがありますので、またどこかで機会がありましたら紹介させていただきたいと思います!

最後まで読んでくださりありがとうございました!
この記事を読んで興味を持って下さった方がいらっしゃればカジュアルにお話させていただきたく、是非ご応募をお願いいたします!!Wantedly / Green