これは Skeet v2 のドキュメントです。

基本アーキテクチャ - Firestore

Skeet Framework は SQL と NoSQL を組み合わせてアプリを構築できます。

PNPM を使ってパッケージを管理し、 TypeScript で型安全な開発を行います。

Skeet Framework の基本的な構造は以下の通りです。

一般的なバックエンドに必要な機能Skeet Framework
データベースFirestore or SQL
ログイン認証Firebase Authentication
APICloud Functions for Firebase 第 2 世代
ロードバランサーCloud Load Balancer
スケジュールタスクCloud Scheduler
Pub/SubCloud Pub/Sub
ドメインCloud DNS
セキュリティCloud Armor

Skeet Framework の基本構造

Skeet Framework はサーバーレスなため、 すぐにファンクションから書き始めることができます。

website, webapp, mobile にフロントエンドのソースコードが配置されます。

functions ディレクトリ以下に Cloud Functions for Firebase のプロジェクトが配置されます。

functions には複数の functions を追加することができます。

bash
root ├── common ├── functions ├── mobile ├── sql ├── webapp ├── website ├── package.json ├── pnpm-workspace.yaml ├── tsconfig.json ├── skeet-cloud.config.json └── vitest.config.ts
ディレクトリ説明
common共通の型や関数、モデル
webappWeb アプリのソースコード
websiteWeb サイトのソースコード
mobileモバイルアプリのソースコード
functionsCloud Functions for Firebase のソースコード
sqlSQL API のソースコード
package.jsonパッケージ管理
skeet-cloud.config.jsonSkeet Framework の設定ファイル
tsconfig.jsonTypeScript の設定ファイル
vitest.config.tsVite テストの設定ファイル
pnpm-workspace.yamlPNPM の設定ファイル
firebase.jsonFirebase の設定ファイル

Skeet Functions の基本構造

Skeet Functions は Cloud Functions for Firebase をベースにしています。 functions ディレクトリ以下に Cloud Functions for Firebase のプロジェクトが配置されます。 functions には複数の functions を追加することができます。

例: functions/skeet-func

bash
. ├── build.ts ├── devBuild.ts ├── dist │ └── index.js ├── nodemon.json ├── package.json ├── src │ ├── index.ts │ ├── lib │ └── routings └── tsconfig.json
ディレクトリ説明
build.tsビルドスクリプト
devBuild.tsビルドスクリプト
distビルド後のソースコード
nodemon.jsonローカルでの起動設定
package.jsonバックエンドのパッケージ管理
srcソースコード
src/index.tsエントリーポイント
src/libライブラリ
src/routingsルーティング
src/scriptsスクリプト
src/utilsユーティリティ
tsconfig.jsonTypeScript の設定

Skeet Functions のインスタンスタイプ

インスタンスタイプ説明
HttpHTTP リクエストを受け取る関数
OnCall関数を呼び出す関数
PubSubPubSub メッセージを受け取る関数
Schedulerスケジュールされた関数
FirestoreFirestore のドキュメントの作成、更新、削除などのトリガーを受け取る関数
AuthFirebase ユーザーアカウントの作成と削除などのトリガーを受け取る関数

Skeet Rountings の基本構造

インスタンスのタイプによって、ルーティングの設定が異なります。 また、Cloud Functions for Firebase のオプション設定は routings/options 以下に配置されています。

bash
├── auth │ ├── authOnCreateUser.ts │ └── index.ts ├── firestore │ ├── firestoreExample.ts │ └── index.ts ├── http │ ├── addUserChatRoomMessage.ts │ ├── createUserChatRoom.ts │ ├── getUserChatRoomMessages.ts │ ├── index.ts │ └── root.ts ├── onCall │ ├── onCallExample.ts │ └── index.ts ├── index.ts ├── options │ ├── authOptions.ts │ ├── firestoreOptions.ts │ ├── httpOptions.ts │ ├── index.ts │ ├── pubsubOptions.ts │ └── schedulerOptions.ts ├── pubsub │ ├── index.ts │ └── pubsubExample.ts └── scheduler ├── index.ts └── schedulerExample.ts

Http インスタンスの設定

Http のデフォルトオプション設定

routings/options/http/httpOptions.ts

ts
import { HttpsOptions } from 'firebase-functions/v2/https' import skeetOptions from '../../../skeetOptions.json' const appName = skeetOptions.name const project = skeetOptions.projectId const region = skeetOptions.region const serviceAccount = `${appName}@${project}.iam.gserviceaccount.com` const vpcConnector = `${appName}-con` const cors = true export const publicHttpOption: HttpsOptions = { region, cpu: 1, memory: '1GiB', maxInstances: 100, minInstances: 0, concurrency: 1, timeoutSeconds: 540, invoker: 'public', labels: { skeet: 'http', }, } export const privateHttpOption: HttpsOptions = { region, cpu: 1, memory: '1GiB', maxInstances: 100, minInstances: 0, concurrency: 80, serviceAccount, ingressSettings: 'ALLOW_INTERNAL_AND_GCLB', vpcConnector, vpcConnectorEgressSettings: 'PRIVATE_RANGES_ONLY', cors, timeoutSeconds: 540, invoker: 'private', labels: { skeet: 'http', }, }

Http インスタンスの設定は、routings/http/{httpInstance} に記述します。

routings/http/root.ts

ts
import { onRequest } from 'firebase-functions/v2/https' import { publicHttpOption } from '@/routings/options' import { TypedRequestBody } from '@/index' import { RootParams } from '@/types/http/rootParams' export const root = onRequest( publicHttpOption, async (req: TypedRequestBody<RootParams>, res) => { try { res.json({ status: 'success', }) } catch (error) { const errorLog = `root - ${error}` console.log(errorLog) res.status(500).json({ status: 'error', message: String(error) }) } } )

Http インスタンスの型定義は、src/types/http/{httpInstance}Params.ts に記述します。

common/types/http/rootParams.ts

ts
export type RootParams = { name?: string }

PubSub インスタンスの設定

PubSub デフォルトオプション設定

routings/options/pubsub/pubsubOptions.ts

ts
import { PubSubOptions } from 'firebase-functions/v2/pubsub' import skeetOptions from '../../../skeetOptions.json' const appName = skeetOptions.name const project = skeetOptions.projectId const region = skeetOptions.region const serviceAccount = `${appName}@${project}.iam.gserviceaccount.com` const vpcConnector = `${appName}-con` export const pubsubDefaultOption = (topic: string): PubSubOptions => ({ topic, region, cpu: 1, memory: '1GiB', maxInstances: 100, minInstances: 0, concurrency: 1, serviceAccount, ingressSettings: 'ALLOW_INTERNAL_ONLY', vpcConnector, vpcConnectorEgressSettings: 'PRIVATE_RANGES_ONLY', timeoutSeconds: 540, labels: { skeet: 'pubsub', }, })

PubSub インスタンスルーティングは、routings/pubsub/{pubsubInstance} に記述します。

routings/pubsub/pubsubExample.ts

ts
import { onMessagePublished } from 'firebase-functions/v2/pubsub' import { pubsubDefaultOption } from '@/routings/options' import { parsePubSubMessage } from '@/lib/pubsub' import { PubsubExampleParams } from '@/types/pubsub/pubsubExampleParams' export const pubsubTopic = 'pubsubExample' export const pubsubExample = onMessagePublished( pubsubDefaultOption(pubsubTopic), async (event) => { try { const pubsubObject = parsePubSubMessage<PubsubExampleParams>(event) console.log({ status: 'success', topic: pubsubTopic, event, pubsubObject, }) } catch (error) { console.error({ status: 'error', message: String(error) }) } } )

PubSub インスタンスの型定義は、src/types/pubsub/{pubsubInstance}Params.ts に記述します。

common/types/pubsub/pubsubExampleParams.ts

ts
export type PubsubExampleParams = { message?: string }

Schedule インスタンスの設定

Schedule デフォルトオプション設定

routings/options/schedule/scheduleOptions.ts

ts
import { ScheduleOptions } from 'firebase-functions/v2/scheduler' import skeetOptions from '../../../skeetOptions.json' const appName = skeetOptions.name const project = skeetOptions.projectId const region = skeetOptions.region const serviceAccount = `${appName}@${project}.iam.gserviceaccount.com` const vpcConnector = `${appName}-con` export const scheduleDefaultOption: ScheduleOptions = { region, schedule: 'every 1 hours', timeZone: 'UTC', retryCount: 3, maxRetrySeconds: 60, minBackoffSeconds: 1, maxBackoffSeconds: 10, serviceAccount, ingressSettings: 'ALLOW_INTERNAL_ONLY', vpcConnector, vpcConnectorEgressSettings: 'PRIVATE_RANGES_ONLY', timeoutSeconds: 540, labels: { skeet: 'schedule', }, }

Schedule インスタンスの設定は、routings/schedule/{scheduleInstance} に記述します。

routings/schedule/scheduleExample.ts

ts
import { onSchedule } from 'firebase-functions/v2/scheduler' import { scheduleDefaultOption } from '@/routings/options' export const scheduleExample = onSchedule( scheduleDefaultOption, async (event) => { try { console.log({ status: 'success' }) } catch (error) { console.log({ status: 'error', message: String(error) }) } } )

Firestore インスタンスの設定

Firestore デフォルトオプション設定

routings/options/firestore/firestoreOptions.ts

ts
import { DocumentOptions } from 'firebase-functions/v2/firestore' import skeetOptions from '../../../skeetOptions.json' const appName = skeetOptions.name const project = skeetOptions.projectId const region = skeetOptions.region const serviceAccount = `${appName}@${project}.iam.gserviceaccount.com` const vpcConnector = `${appName}-con` export const firestoreDefaultOption = (document: string): DocumentOptions => ({ document, region, cpu: 1, memory: '1GiB', maxInstances: 100, minInstances: 0, concurrency: 1, serviceAccount, ingressSettings: 'ALLOW_INTERNAL_ONLY', vpcConnector, vpcConnectorEgressSettings: 'PRIVATE_RANGES_ONLY', timeoutSeconds: 540, labels: { skeet: 'firestore', }, })

Firestore インスタンスの設定は、routings/firestore/{firestoreInstance} に記述します。

routings/firestore/firestoreExample.ts

ts
import { onDocumentCreated } from 'firebase-functions/v2/firestore' import { firestoreDefaultOption } from '@/routings/options' export const firestoreExample = onDocumentCreated( firestoreDefaultOption('User/{userId}'), (event) => { console.log('firestoreExample triggered') try { console.log(event.params) } catch (error) { console.log({ status: 'error', message: String(error) }) } } )

Firestore Trigger のタイプ

イベントタイプトリガー
onDocumentCreatedドキュメントが作成されたとき
onDocumentDeletedドキュメントが削除されたとき
onDocumentUpdatedドキュメントが更新されたとき
onDocumentWrittenドキュメントが作成、更新、削除されたとき

Auth インスタンスの設定

Auth デフォルトオプション設定

routings/options/auth/authOptions.ts

ts
import { RuntimeOptions } from 'firebase-functions/v1' import skeetOptions from '../../../skeetOptions.json' const appName = skeetOptions.name const project = skeetOptions.projectId const serviceAccount = `${appName}@${project}.iam.gserviceaccount.com` const vpcConnector = `${appName}-con` export const authPublicOption: RuntimeOptions = { memory: '1GB', maxInstances: 100, minInstances: 0, timeoutSeconds: 300, labels: { skeet: 'auth', }, } export const authPrivateOption: RuntimeOptions = { memory: '1GB', maxInstances: 100, minInstances: 0, timeoutSeconds: 300, serviceAccount, ingressSettings: 'ALLOW_INTERNAL_ONLY', vpcConnector, vpcConnectorEgressSettings: 'PRIVATE_RANGES_ONLY', labels: { skeet: 'auth', }, }

Auth インスタンスのデフォルトファンクションでは、 Firebase ユーザーが作成されたときに、 ユーザーのドキュメントを作成します。

Auth インスタンスの設定は、routings/auth/auth{MethoName}.ts に記述します。

routings/auth/authOnCreateUser.ts

ts
import { User } from '@/models' import { addCollectionItem } from '@skeet-framework/firestore' import * as functions from 'firebase-functions/v1' import { authPublicOption } from '@/routings' import { gravatarIconUrl } from '@/utils/placeholder' import skeetConfig from '../../../skeetOptions.json' const region = skeetConfig.region export const authOnCreateUser = functions .runWith(authPublicOption) .region(region) .auth.user() .onCreate(async (user) => { try { const { uid, email, displayName, photoURL } = user const userParams = { uid, email: email || '', username: displayName || email?.split('@')[0] || '', iconUrl: photoURL == '' || !photoURL ? gravatarIconUrl(email ?? '[email protected]') : photoURL, } const userRef = await addCollectionItem<User>('User', userParams, uid) console.log({ status: 'success', userRef }) } catch (error) { console.log({ status: 'error', message: String(error) }) } })

Dev 環境での Firebase ユーザー登録・ログイン

Dev 環境では、 Firebase ユーザーの登録・ログインに、 skeet login コマンドを使用します。

Skeet アプリを起動します。

bash
$ skeet s

別ウィンドウでターミナルを開き、 skeet login コマンドを実行します。

bash
$ skeet login

画像

Firebase ユーザー登録と Firestore ユーザー登録が完了します。

コンソールに表示される コードをコピーしてターミナルに貼り付けます。

ACCESS_TOKEN が環境変数に設定されます。

モデルの定義

モデルの定義は、 コレクションのツリー構造を

common/models/{modelName}Models.ts

に記述します。

また、skeet ai コマンドを使用して、firestore のデータモデルを自動生成することができます。 以下のコマンドで Skeet AI Firestore モードを起動し、作成したいモデルの説明を入力します。

bash
$ skeet ai --mode ? 🤖 Select Mode prisma typedoc ❯ firestore function method 🔥 Firestore Model Generating Mode 🔥 ? Please describe your Firestore use case. e.g. I want to create a blog app. You:

型定義には Firestore Data Converter を使用しています。

NoSQL データモデルは非常に柔軟であるため、 モデルの定義は必須ではありませんが、

それぞれのモデルに

  • CollectionId
  • DocumentId

をコメントで記述しておくことを推奨します。 可読性が上がり、

さらに CodePilot でのコード補完が効くようになります。

common/models/userModels.ts

ts
import { Timestamp, FieldValue } from '@skeet-framework/firestore' // CollectionId: User // DocumentId: auto // Path: User export const UserCN = 'User' export const genUserPath = () => `${UserCN}` export type User = { id?: string uid: string username: string email: string iconUrl: string userChatRoomIds?: string[] createdAt?: Timestamp | FieldValue updatedAt?: Timestamp | FieldValue }

データの取得、更新、削除は、 @skeet-framework/firestore プラグインを使用して行います。

ts
import { add, get } from '@skeet-framework/firestore'

詳しくは、Skeet Firestore を参照してください。

Skeet CLI

Skeet CLI を使って新たに Cloud Functions for Firebase を追加したり、 yarn コマンドを 各ファンクションごとに実行することができます。

コマンド一覧

bash
Usage: skeet [options] [command] CLI for Skeet - Full-stack TypeScript Serverless framework Options: -V, --version output the version number -h, --help display help for command Commands: create [options] <appName> Create Skeet Framework App server|s [options] Run Skeet App deploy [options] Deploy Skeet APP to Firebase init [options] Initialize Google Cloud Setups login Skeet Login Command - Create Firebase Login Token curl [options] <methodName> Skeet Curl Command - Call Firebase Functions Endpoint g|generate Skeet Generate Comannd log [options] Deploy Skeet APP to Firebase docker Docker commands db Database commands iam Skeet IAM Comannd to setup Google Cloud Platform add Skeet Add Comannd to add new functions sync Skeet Sync Comannd to sync backend and frontend delete|d Skeet Delete Command get Get Skeet App List ai [options] Call Skeet AI Assistant config Config commands run [options] Run commands new|n [options] Create Skeet Framework App console|c Call Firebase Console to Test Functions check Check Cloud Configurations test Run tests help [command] display help for command