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

基本アーキテクチャ - Firestore

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

ここでは、NoSQL のバックエンドを構築するための基本構造を説明します。

Skeet Framework Firestore バックエンドの基本的な構造は以下の通りです。

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

Skeet Framework の基本構造

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

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

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

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

bash
├── src │ ├── public │ └── types ├── functions │ └── skeet ├── package.json ├── skeet-cloud.config.json └── firebase.json
ディレクトリ説明
srcフロントエンドのソースコード
src/publicフロントエンドのソースコード
src/typesフロントエンドの型定義
functionsCloud Functions for Firebase のソースコード
functions/skeetOpenAI API 等に関する functions
package.jsonバックエンドのパッケージ管理
skeet-cloud.config.jsonSkeet Framework の設定ファイル
firebase.jsonFirebase の設定ファイル

Skeet Functions の基本構造

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

例: functions/skeet

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

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

インスタンスタイプ説明
HttpHTTP リクエストを受け取る関数
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 ├── 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, 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, 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', message: 'Skeet Backend is running!', name: req.body.name || 'Anonymous', }) } 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 に記述します。

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 に記述します。

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 が環境変数に設定され、

skeet curl コマンドが使用できるようになります。

bash
$ skeet help curl Usage: skeet curl [options] <methodName> Skeet Curl Command - Call Cloud Functions Endpoint for Dev Arguments: methodName Method Name - e.g. skeet curl createUserChatRoom Options: -d,--data [data] JSON Request Body - e.g. '{ "model": "gpt4", "maxTokens": 420 }' -r, --raw Show chunk data (default: false) -p, --production For Production (default: false) -f,--functions [functions] For Production Functions Name (default: false) -h, --help display help for command

モデルの定義

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

src/models/{modelName}Models.ts

に記述します。

型定義には Typesaurus を使用しています。

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

それぞれのモデルに

  • CollectionId
  • DocumentId

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

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

models/userModels.ts

ts
import { Ref, Timestamp } from '@skeet-framework/firestore' // Define Collection Name export const userCollectionName = 'User' export const userChatRoomCollectionName = 'UserChatRoom' export const userChatRoomMessageCollectionName = 'UserChatRoomMessage' // CollectionId: User // DocumentId: uid export type User = { uid: string username: string email: string iconUrl: string createdAt?: Timestamp updatedAt?: Timestamp } // CollectionId: UserChatRoom // DocumentId: auto export type UserChatRoom = { userRef: Ref<User> title: string model: string maxTokens: number temperature: number stream: boolean createdAt?: Timestamp updatedAt?: Timestamp } // CollectionId: UserChatRoomMessage // DocumentId: auto export type UserChatRoomMessage = { userChatRoomRef: Ref<UserChatRoom> role: string content: string createdAt?: Timestamp updatedAt?: Timestamp }

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

ts
import { addCollectionItem, getCollectionItem, } from '@skeet-framework/firestore'

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

Skeet CLI

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

コマンド一覧

bash
$ skeet --help Usage: skeet [options] [command] CLI for Skeet - Open-Source Serverless App Framework Options: -V, --version output the version number -h, --help display help for command Commands: create <appName> Create Skeet Framework App server|s Run Skeet App deploy Deploy Skeet APP to Firebase init [options] Initialize Google Cloud Setups for Skeet APP iam Skeet IAM Comannd to setup Google Cloud Platform yarn [options] <yarnCmd> Skeet Yarn Comannd to run yarn command for multiple functions add Skeet Add Comannd to add new functions sync Skeet Sync Comannd to sync backend and frontend delete|d Skeet Delete Command login [options] Skeet Login Command - Create Firebase Login Token get Get Skeet App List curl [options] <methodName> Skeet Curl Command - Call Cloud Functions Endpoint for Dev test Skeet Jest Test Command help [command] display help for command