Next.jsでFirestoreのCRUD操作をやってみる

2022/03/21に公開

本記事でやりたいこと

Next.jsでFirestoreのCRUD操作をする

本記事のリポジトリ

https://github.com/sugayutokyo/nextjs-firestore-json
うまく行かない処理などありましたら、こちらをご覧ください!

Firebaseの設定

Firebaseにログインする

下記リンクからお手持ちのGoogleアカウントでログインしてください。
https://firebase.google.com/products/firestore/?hl=ja&gclid=EAIaIQobChMIgdOrncS79gIVTUNgCh2yzQ-GEAAYASAAEgLGJ_D_BwE&gclsrc=aw.ds

Firebaseプロジェクトを作成

作成済みの場合は飛ばしてしまって問題ありません!

  1. Firebaseプロジェクトを作成します

  2. プロジェクト名を入力し、「Firebaseの規約に同意します」にチェックを入れて「続行」押下

  3. 「Googleアナリティクスを有効にする」はお好みで!不要なら無効で問題ありません!

    「アナリティクスの地域」→日本に変えて、「Googleアナリティクス利用規約に同意します。」にチェックを入れて、「プロジェクトを作成」押下

  4. プロジェクトが作成し終わったら、「続行」押下

Firebase Admin SDKの秘密鍵を作成する

  1. 画像の赤枠のアイコン(歯車)押下

  2. 「プロジェクトの設定」押下

  3. 「サービスアカウント」タブ押下→「サービスアカウントを作成」押下

  4. Node.jsが選択されていることを確認して、「新しい秘密鍵の生成」押下

  5. 「キーを生成」押下

  6. jsonファイルがダウンロードされたら完了(後で使用するため保管してください)

Firestoreの設定

データベースを作成

  1. 構築→「Firestore Database」押下

  2. 「データベースの作成」押下

  3. 「本番環境モード」が選択されていることを確認して、「次へ」押下

  4. 「Cloud Firestoreのロケーション」で「asia-northeast1」を選択して「有効にする」押下

ロケーションに関して他のものを選択したい場合は下記を参考にしてください
https://firebase.google.com/docs/firestore/locations?hl=ja

  1. 下記画像のように表示されれば完了

コレクションを作成

まだコレクションが存在しないため作成します。

  1. 「コレクションを開始」押下

  2. 「コレクションID」にusersと入力し「次へ」押下

  3. 「自動ID」押下でドキュメントIDを自動で割り当てる

  4. 「保存」押下

  5. 下記画像のようにコレクションが作成されれば完了

Next.jsの環境構築

## Next.jsのプロジェクトを作成
$ npx create-next-app . -e with-tailwindcss

## npmモジュールを生成
$ npm i

## ローカルサーバー起動
$ npm run dev

下記画像のように表示されれば完了

本記事では prettier を導入しています。導入したい方は以下の YAML ファイルを参考に設定してください!

.prettierrc.yml
printWidth: 80
tabWidth: 2
semi: true
singleQuote: true
quoteProps: as-needed
jsxSingleQuote: false
trailingComma: all
bracketSpacing: true
jsxBracketSameLine: true
arrowParens: avoid
endOfLine: lf

Firebase Admin SDKの秘密鍵をNext.jsに反映する

  1. 「Firebase Admin SDKの秘密鍵を作成する」で作成したjsonファイルをリネームする
    本記事では「firebase-test-serviceAccount.json」とリネームしています。
    ※ リネームはしなくても大丈夫です!分かりやすくするためにリネームしています。

  2. .gitignoreに追加してgit上の管理から外す

.gitignore
firebase-test-serviceAccount.json
  1. firebase-test-serviceAccount.jsonファイルをNext.jsのルートにコピーする
    gitignoreに追加しているためgitの管理から外れているはずです。

実際にCRUD操作を行なってみる

前準備

  1. Firebase Admin SDK、axiosをインストールする
$ npm i firebase-admin
$ npm i axios
  1. apiのファイルを作成する
$ touch pages/api/user.ts

insert

  1. Firestoreにデータをinsertするコードをuser.tsに記述
pages/api/user.ts
import type { NextApiRequest, NextApiResponse } from 'next';
const { cert } = require('firebase-admin/app');
const { getFirestore } = require('firebase-admin/firestore');
const serviceAccount = require('../../firebase-test-serviceAccount.json'); // 秘密鍵を取得
const admin = require('firebase-admin');

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse,
) {
  const COLLECTION_NAME = 'users';
  // 初期化する
  if (admin.apps.length === 0) {
    admin.initializeApp({
      credential: cert(serviceAccount),
    });
  }
  const db = getFirestore();

  if (req.method === 'POST') {
    const docRef = db.collection(COLLECTION_NAME).doc();
    const insertData = {
      datano: '1',
      name: 'Symfo',
      email: 'symfo@example.com',
    };
    docRef.set(insertData);
  }
  res.status(200);
}
  1. トップページにinsertを実行するボタンを作成
    index.tsxを下記コードで全て書き換える
pages/index.tsx
import type { NextPage } from 'next';
import axios from 'axios';

const Home: NextPage = () => {
  const insertUser = async () => {
    await axios.post('/api/user');
  };

  return (
    <div className="flex min-h-screen flex-col items-center justify-center py-2">
      <button
        className="mt-4 w-60 rounded-full bg-green-500 py-2 px-4 font-bold text-white hover:bg-green-700"
        onClick={() => insertUser()}>
        Insert User
      </button>
    </div>
  );
};

export default Home;
  1. トップ画面の「Insert User」押下でFirestoreにデータができていれば完了

update

  1. 更新対象のdocument idを控える(赤枠内の文字列)

  2. データをupdateするコードをuser.tsに記述(1.で控えたdocument idをtargetDocに設定する)

pages/api/user.ts
import type { NextApiRequest, NextApiResponse } from 'next';
const { cert } = require('firebase-admin/app');
const { getFirestore } = require('firebase-admin/firestore');
const serviceAccount = require('../../firebase-test-serviceAccount.json'); // 秘密鍵を取得
const admin = require('firebase-admin');

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse,
) {
  const COLLECTION_NAME = 'users';
  // 初期化する
  if (admin.apps.length === 0) {
    admin.initializeApp({
      credential: cert(serviceAccount),
    });
  }
  const db = getFirestore();
+ const targetDoc = 'uxngvlaMwDc0Ye2WnQhN'; //書き換える
  if (req.method === 'POST') {
    const docRef = db.collection(COLLECTION_NAME).doc();
    const insertData = {
      datano: '1',
      name: 'Symfo',
      email: 'symfo@example.com',
    };
    docRef.set(insertData);
+ } else if (req.method === 'PATCH') {
+   const docRef = db.collection(COLLECTION_NAME).doc(targetDoc);
+   const updateData = {
+     datano: '1',
+     name: 'updateSynfo',
+     email: 'updateSynfo@example.com',
+   };
+   docRef.set(updateData);
  }
  res.status(200);
}

  1. トップページにupdateを実行するボタンを作成
pages/index.tsx
import type { NextPage } from 'next';
import axios from 'axios';

const Home: NextPage = () => {
  const insertUser = async () => {
    await axios.post('/api/user');
  };
+ const updateUser = async () => {
+   await axios.patch('/api/user');
+ };

  return (
    <div className="flex min-h-screen flex-col items-center justify-center py-2">
      <button
        className="mt-4 w-60 rounded-full bg-green-500 py-2 px-4 font-bold text-white hover:bg-green-700"
        onClick={() => insertUser()}>
        Insert User
      </button>
+    <button
+      className="mt-4 w-60 rounded-full bg-yellow-500 py-2 px-4 font-bold text-white hover:bg-yellow-700"
+      onClick={() => updateUser()}>
+      Update User
+    </button>
    </div>
  );
};

export default Home;
  1. トップ画面の「Update User」押下でFirestoreのデータが更新されていれば完了

get

  1. データをgetするコードをuser.tsに記述
pages/api/user.ts
...
export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse,
) {
  const COLLECTION_NAME = 'users';
  // 初期化する
  if (admin.apps.length === 0) {
    admin.initializeApp({
      credential: cert(serviceAccount),
    });
  }
  const db = getFirestore();

  const targetDoc = 'uxngvlaMwDc0Ye2WnQhN'; //書き換える
  if (req.method === 'POST') {
    ...
  } else if (req.method === 'PATCH') {
    const docRef = db.collection(COLLECTION_NAME).doc(targetDoc);
    const updateData = {
      datano: '1',
      name: 'updateSynfo',
      email: 'updateSynfo@example.com',
    };
    docRef.set(updateData);
+ } else if (req.method === 'GET') {
+   const doc = await db.collection(COLLECTION_NAME).doc(targetDoc).get();
+   console.log(doc);
  }
  res.status(200);
}
  1. トップページにgetを実行するボタンを作成
pages/index.tsx
import type { NextPage } from 'next';
import axios from 'axios';

const Home: NextPage = () => {
  const insertUser = async () => {
    await axios.post('/api/user');
  };
  const updateUser = async () => {
    await axios.patch('/api/user');
  };
+ const getUser = async () => {
+   await axios.get('/api/user');
+ };

  return (
    <div className="flex min-h-screen flex-col items-center justify-center py-2">
      <button
        className="mt-4 w-60 rounded-full bg-green-500 py-2 px-4 font-bold text-white hover:bg-green-700"
        onClick={() => insertUser()}>
        Insert User
      </button>
      <button
        className="mt-4 w-60 rounded-full bg-yellow-500 py-2 px-4 font-bold text-white hover:bg-yellow-700"
        onClick={() => updateUser()}>
        Update User
      </button>
+    <button
+      className="mt-4 w-60 rounded-full bg-blue-500 py-2 px-4 font-bold text-white hover:bg-blue-700"
+      onClick={() => getUser()}>
+      Get User
+    </button>
    </div>
  );
};

export default Home;
  1. トップ画面の「Get User」押下でFirestoreのデータがターミナルに表示されていれば完了

    下記のように_fieldsProtoにFirestoreに格納されている内容がターミナルに表示されていればOK
QueryDocumentSnapshot {
  _fieldsProto: {
    name: { stringValue: 'updateSynfo', valueType: 'stringValue' },
    email: {
      stringValue: 'updateSynfo@example.com',
      valueType: 'stringValue'
    },
    datano: { stringValue: '1', valueType: 'stringValue' }
  },
  _ref: DocumentReference {
    _firestore: Firestore {
      _settings: [Object],
      _settingsFrozen: true,
      _serializer: [Serializer],
      _projectId: 'next-js-firestore-json',
      registeredListenersCount: 0,
      bulkWritersCount: 0,
      _backoffSettings: [Object],
      _clientPool: [ClientPool]
    },
    _path: ResourcePath { segments: [Array] },
    _converter: {
      toFirestore: [Function: toFirestore],
      fromFirestore: [Function: fromFirestore]
    }
  },
  _serializer: Serializer {
    createReference: [Function (anonymous)],
    createInteger: [Function (anonymous)],
    allowUndefined: false
  },
  _readTime: Timestamp { _seconds: 1647837063, _nanoseconds: 504465000 },
  _createTime: Timestamp { _seconds: 1647831093, _nanoseconds: 420866000 },
  _updateTime: Timestamp { _seconds: 1647836054, _nanoseconds: 546874000 }
}

delete

  1. データをdeleteするコードをuser.tsに記述
pages/api/user.ts
...

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse,
) {
  ...

  const targetDoc = 'uxngvlaMwDc0Ye2WnQhN'; //書き換える
  if (req.method === 'POST') {
    ...
  } else if (req.method === 'PATCH') {
    ...
  } else if (req.method === 'GET') {
    ...
+ } else if (req.method === 'DELETE') {
+   const doc = await db.collection(COLLECTION_NAME).doc(targetDoc).delete();
  }
  res.status(200);
}
  1. トップページにdeleteを実行するボタンを作成
pages/index.tsx
import type { NextPage } from 'next';
import axios from 'axios';

const Home: NextPage = () => {
  const insertUser = async () => {
    await axios.post('/api/user');
  };
  const updateUser = async () => {
    await axios.patch('/api/user');
  };
  const getUser = async () => {
    await axios.get('/api/user');
  };
+ const deleteUser = async () => {
+   await axios.delete('/api/user');
+ };

  return (
    <div className="flex min-h-screen flex-col items-center justify-center py-2">
      <button
        className="mt-4 w-60 rounded-full bg-green-500 py-2 px-4 font-bold text-white hover:bg-green-700"
        onClick={() => insertUser()}>
        Insert User
      </button>
      <button
        className="mt-4 w-60 rounded-full bg-yellow-500 py-2 px-4 font-bold text-white hover:bg-yellow-700"
        onClick={() => updateUser()}>
        Update User
      </button>
      <button
        className="mt-4 w-60 rounded-full bg-blue-500 py-2 px-4 font-bold text-white hover:bg-blue-700"
        onClick={() => getUser()}>
        Get User
      </button>
+     <button
+       className="mt-4 w-60 rounded-full bg-red-500 py-2 px-4 font-bold text-white hover:bg-red-700"
+       onClick={() => deleteUser()}>
+       Delete User
+     </button>
    </div>
  );
};

export default Home;
  1. トップ画面の「Delete User」押下でFirestoreのデータが削除されていればOK

お疲れ様でした!

参考

GitHubで編集を提案

Discussion