🔥

jsのobjectからFirestoreのコード、セキュリティールールなどを生成するcode generatorを作ってみた

2024/08/27に公開

モチベーション

(^ω^≡^ω^) 個人開発で爆速でwebアプリを作りたい!

(^ω^≡^ω^) だったらとりあえずFirebaseや!Firestoreにとりあえずデータ突っ込んだる!

(^ω^≡^ω^) よし、Firestoreのクライアントコードを書こう!

(•́﹏•̀) あれ?Firestoreのクライアントコードって、型付けとか、セキュリティルールとか、結構面倒じゃね?

c(╹ω╹*c[___] 萎えた。。。

c('ω'*c[___] めんどくさいし、なんか一つの設定ファイルに書いたらよしなに全部自動生成してくれるやつ誰か作ってくれよ・・・

(¦3ꇤ[▓▓] どこにもない・・・

(:3っ)っ ー=三 [___] しかたねぇ!作ったるか!

Firestoreのつらいとこ

FirestoreはNoSQLで、RDBMSと違って面倒な運用作業が少ないのが利点ですが、いざ開発を始めると、

  • documentの型付け
  • セキュリティルールの記述
  • 複合インデックスの記載

など、なんだかんだで面倒な作業が多いです。しかもこれら自体がすべて手打ちで作成しなければならないので、セキュリティルール - documentの型付けなどで不整合が発生したりなど、結構大変
(複合インデックスは自動で作ってくれるurlを生成してくれたりはしますが)

コードジェネレータを作った!

だったら、一つのスキーマファイルから、Firestoreのtypescriptコードとセキュリティルールを生成できたらいいのでは?と思い、FirestoreのコードジェネレータをDenoで作りました!

https://github.com/u-yas/burning-firestore

burning-firestoreという名前です。
なんか深夜テンションということもあり、burningな気分だったので、burning-firestoreと名付けました。バーニング!🔥

このburning-firestoreを使えば、JSのオブジェクトから

  • Firestore(web modular api)のtypescriptコード
  • Firestoreのセキュリティルール

を生成できます!

  • Firestoreの複合インデックス(これから頑張る👷‍♂️)
    はまだ対応しておりませんが、新規にサポート予定です

サンプルコードはこんな感じです

https://github.com/u-yas/burning-firestore/blob/main/examples/burning.ts

上記のtsファイルを、tsxなり、bunなり、denoなりで実行すると。。。

import { DocumentReference,Timestamp } from 'firebase/firestore';



export interface User {
	name: string;
	email: string;
	age?: number;
	isActive: boolean;
	createdAt: Timestamp;
	preferences?: {
    theme?: string;
    notifications?: boolean;
  };
}

export type Ref = (userDocId?: string) => string;

export const userRef: Ref = (userDocId?: string) => `/users${userDocId ? `/${userDocId}` : ''}`;
import { Firestore, collection, doc, getDoc, getDocs, setDoc, addDoc, updateDoc, deleteDoc, query, QueryFieldFilterConstraint, QuerySnapshot, DocumentData } from 'firebase/firestore';
import { User, userRef, Ref } from './scheme';

// Collection reference
export const userCollection = (db: Firestore, ...params: Parameters<Ref>) => 
  collection(db, userRef(...params));

// Document reference
export const userDoc = (db: Firestore, ...params: Parameters<Ref>) => 
  doc(db, userRef(...params));

// Get a document
export const getUser = async (db: Firestore, ...params: Parameters<Ref>) => {
  const docRef = userDoc(db, ...params);
  const docSnap = await getDoc(docRef);
  return docSnap.exists() ? docSnap.data() as User : null;
};

// Get all documents in a collection
export const getAllUsers = async (db: Firestore, ...params: Parameters<Ref>) => {
  const collectionRef = userCollection(db, ...params);
  const querySnapshot = await getDocs(collectionRef);
  return querySnapshot.docs.map(doc => doc.data() as User);
};

// Add a new document
export const addUser = async (db: Firestore, data: User, ...params: Parameters<Ref>) => {
  const collectionRef = userCollection(db, ...params);
  return await addDoc(collectionRef, data);
};

// Set a document
export const setUser = async (db: Firestore, data: User, ...params: Parameters<Ref>) => {
  const docRef = userDoc(db, ...params);
  await setDoc(docRef, data);
};

// Update a document
export const updateUser = async (db: Firestore, data: Partial<User>, ...params: Parameters<Ref>) => {
  const docRef = userDoc(db, ...params);
  await updateDoc(docRef, data);
};

// Delete a document
export const deleteUser = async (db: Firestore, ...params: Parameters<Ref>) => {
  const docRef = userDoc(db, ...params);
  await deleteDoc(docRef);
};

export const queryUsers = 
  (db: Firestore, ...refParams: Parameters<Ref>) =>
  async (...queries: QueryFieldFilterConstraint[]) => {
    const collectionRef = userCollection(db, ...refParams);
    const q = query(collectionRef, ...queries);
    const querySnapshot = await getDocs(q);
    return querySnapshot.docs.map((doc) => doc.data() as User);
  };

みたいなコードが生成されます!

型名や各変数名は勢いで作ったので、もう少しいい感じにしたいですね。

また、firestore.rulesも生成されます

https://github.com/u-yas/burning-firestore/blob/main/examples/firestore.rules

Burning Firestoreの技術構成

基本、Denoのみで作成しています。いいですねDeno。LinterとかFormatterとか何も考えずにすぐに開発に入れて開発者体験最高でした。

jsのスキーマオブジェクトから無理やり解釈してtemplateの文字列にコードを埋め込んでいます。かなりの力技でやっているので、色々ぬけもれなどがありそう。prお待ちしてます!

ただ、こういったものはまずある程度動くものを一旦完成させることが大事だと思っているので、まずは完成させて、その後改善していくのがいいかな~と思っています。一度作ってしまえば後は小さな改善を繰り返すのみ!!はじめから完璧なものは目指しません。

また、npmへのpublishには@deno/dntというライブラリを利用しています

https://github.com/denoland/dnt

これが相当便利で、typescriptで簡単な設定objectを書いて実行するだけで、nodeで実行可能なnpmへのpublishが直ぐにできるコードが生成されます。最高すぎる。これだけでdenoを使う理由になるくらい便利でした

今後の展望

現在考えているのは

  • Firestoreの複合インデックスの生成
  • Firestoreのセキュリティルールの未完成な部分の実装
    • ネストしたオブジェクトの検証や、セキュリティルールの複雑な関数が利用できるようなプリセットの実装
  • Firestoreのクライアントコードのvalidation helper(valibotとかzodとか)の生成
  • firebase-adminのクライアントコードの生成
  • flutterやgoなどの多言語対応

などがあります。一歩一歩改善していきたいですね。

ぜひ、使ってみてください!

Discussion