HonoのRPC機能を使いつつ、レポジトリを分割する方法を検討してみた
動機
SHIFTのKoshiishiです。
現在、あるアプリケーションがバックエンド: Express、フロントエンド: Vue という構成で動いており、モノレポで管理されています。
モノレポ自体は問題ではないのですが、このアプリケーションが担う責務が複数にわたり、アプリケーションのアプリケーションのような構成になってしまっています。
また基本的にJavaScriptアプリケーションであるため、型ファイルを手書きして部分的に補完を利かせているもののそのファイルの更新コストが大きいという問題もあります。
そこで、大きく以下の方向性で技術スタックの刷新を検討しています:
バックエンドを Express → Hono に置き換え
フロントエンドを Vue → React に置き換え
モノレポからリポジトリ分割へ移行し
バックエンドは 1 つのリポジトリで管理 (BackendRepo)
フロントエンドは複数リポジトリに分割 (FrontendRepoX, FrontendRepoY, ...)
分割することで、チーム構成やプロダクト特性に応じてリポジトリ単位の責任範囲をはっきりさせたいと考えています。また、バックエンドのAPIの型をフロントエンドに渡すことで、開発生産性の向上を期待しています。
現状厚めのテストでカバーしている範囲の一部を、徐々に静的な検査に寄せていく狙いがあります。
静的解析でカバーできる品質の部分とテストでカバーすべき部分は完全に置き換え可能なものではありませんが、静的検査を活用してバリデーションの実装を強固にし、よりドメインに集中したテストに注力していきたいです。
この記事でやってみたこと
簡素なTodoアプリのバックエンドをHonoで作成し、GitHub PackagesのnpmレジストリにAPIの型を登録することでHonoのRPC機能を使いつつも、レポジトリが分割可能であることを確かめてみました。
最終的なコードはこちらにあります。
userとtodoのテーブルがあり、作成と一覧APIのみがあります。
Honoプロジェクトの作成
まずはバックエンド側のプロジェクトを Hono + TypeScript で新規作成します。 ここでは Bun を利用してプロジェクト生成から開発を行います。
❯ bun create hono@latest hono-todo-backend
create-hono version 0.14.3
✔ Using target directory … hono-todo-backend
? Which template do you want to use? nodejs
? Do you want to install project dependencies? yes
? Which package manager do you want to use? bun
✔ Cloning the template
✔ Installing project dependencies
🎉 Copied project files
Get started with: cd hono-todo-backend
上記で hono-todo-backend ディレクトリが生成され、簡単なサンプルの Hono アプリが用意されます。
この段階で、cd hono-todo-backend して Bun 開発サーバを起動すれば動作確認が可能です。
注意: 今回は手軽さを優先して root ユーザーで作業し、.env と docker-compose.yml を用意して進めています。またパスワードなども文字列でベタ書きしている場面がありますが、実際の運用ではよりセキュアな管理を行ってください。
Docker と Prisma のセットアップ
Docker コンテナで DB を立ち上げる
docker-compose.yml を用意します
services:
db:
image: mysql:8.0
container_name: todo-mysql
restart: unless-stopped
environment:
MYSQL_ROOT_PASSWORD: password
MYSQL_DATABASE: todo_db
ports:
- "3306:3306"
command: --default-authentication-plugin=mysql_native_password
その後、 docker compose up -d でデータベースコンテナを起動します。
Prisma の導入
次に、Hono プロジェクト内で ORM として Prisma を利用するために、以下のコマンドを実行します。
❯ bun add prisma @prisma/client
❯ bun x prisma init
prisma init を実行すると、プロジェクト内に prisma/ ディレクトリと prisma/schema.prisma、および .env ファイルが生成されます。 .env にはデータベース接続情報 (DATABASE_URL) を記載します。
Prisma スキーマの編集
prisma/schema.prisma が生成されますが。
今回previewFeaturesの一つであるprismaSchemaFolderをオンにして、prisma/フォルダ以下にprismaファイルを分けてみました。
参考: How to enable multi-file Prisma schema support schema.prismaに設定を、todo.prisma,user.prismaではそれぞれ定義を行いました。
詳細はこちら
❯ tree prisma/
prisma/
└── schema
├── schema.prisma
├── todo.prisma
└── user.prisma
マイグレーション実行
❯ bun x prisma migrate dev --name init
これで初回のマイグレーションが走り、DBにテーブルが作成されます。
❯ bun x prisma generate
prisma clientを生成します
APIの実装と型のexport
(本来はzodによるバリデーション等行うべきですが、今回は動作の検証が目的なので雑なバリデーションにしています)
TodoアプリのAPIの一部として、ユーザー, Todoの追加と一覧だけを雑に実装します
Todoの追加と一覧
export const todoApiRoutes = app.get('/todo', async (c) => {
const todos = await prisma.todo.findMany()
return c.json(todos)
}).post('/todo', async (c) => {
const body = await c.req.json()
const { title, userId } = body
if (!title || !userId) {
return c.json({ error: 'title and userId are required' }, 400)
}
const newTodo = await prisma.todo.create({
data: {
title: title as string,
userId: Number(userId),
},
})
return c.json(newTodo, 201)
})
userの追加と一覧
export const userApiRoutes = app.post('/user', async (c) => {
const body = await c.req.json()
const { name, email, password } = body
if (!name || !email || !password) {
return c.json({ error: 'name, email, password are required' }, 400)
}
const newUser = await prisma.user.create({
data: {
name: name as string,
email: email as string,
password: password as string,
},
})
return c.json(newUser, 201)
}).get('/user', async (c) => {
const users = await prisma.user.findMany()
return c.json(users)
})
index.tsで型をexportします
export type TodoApiRoutes = typeof todoApiRoutes
export type UserApiRoutes = typeof userApiRoutes
GitHubPackagesに登録(バックエンド側)
こちらの記事 を参考に
GitHubからtokenを作成し, .npmrcに
@{GitHubのユーザー名}:registry=https://npm.pkg.github.com
//npm.pkg.github.com/:_authToken={ここにtokenを書きます}
を記載します。
.npmrcは.gitignoreに記載します。 dist以下に型を吐くように設定した上で
❯ bun run build
❯ npm publish
します。
(二回目以降はバージョンが重複しないようお気を付けください)
publish時にエラーがなければ、個人のプライベートなPackageとして登録されます。
GitHubPackagesから引いてくる(フロント側)
.npmrcに同様に
@{GitHubのユーザー名}:registry=https://npm.pkg.github.com
//npm.pkg.github.com/:_authToken={ここにtokenを書きます}
を設定し、.gitignoreに記載します。
この時tokenは同一である必要はなく、readだけのtokenを発行する形の方が良いと思います。
.npmrcが存在する状態で
❯ bun install @{GitHubユーザー名}/hono-todo-backend
することでプライベートパッケージをインストールすることができます。
補完が効いていることを確認
レスポンスにはschemaファイルで決めた型が、クライアントの補完には実装したメソッドが保管されることが確認できました。
おわりに
honoのRPC機能を使いつつも、レポジトリを分割出来ることが確認できました。
今後はチームでの活用に向けて
GitHub Enterpriseのチーム内のPackagesへの登録
npm publishのGitHubActionsでの自動化 等をやっていきます。
参考
見よ、これがHonoのRPCだ
GitHub Packagesのnpm registryを利用してパッケージを公開してみた
お問合せはお気軽に
SHIFTについて(コーポレートサイト)
SHIFTのサービスについて(サービスサイト)
SHIFTの導入事例
お役立ち資料はこちら
SHIFTの採用情報はこちら
https://recruit.shiftinc.jp/career/
PHOTO:UnsplashのFotis Fotopoulos