🔥

How to use Better Auth with Hono + D1

に公開

最初の雑談みたいなやつ

お久しぶりです。
今回は、私たちが作成しているサービスで、Better Authを使用した認証機能を使用することになり、HonoとD1を使ってそれを実現したので共有しようと思いました。

D1というのがなかなか扱いが難しいので、Better Authの実装に関する記事などを探しても出てきませんでしたので、この記事が誰かの助けになればと思います。

サンプルのリポジトリはこちらです。exampleに変えているところを正しいものに変更して使用してください。(またはリポジトリをAIに読み込ませて自分の好みのものを作成してください)

https://github.com/shinaps/better-auth-hono-d1

なぜBetter Auth + Hono + D1を使用した認証機能を使用することにしたのか

私たちが開発しているサービス「リピめし」では元々Supabaseの認証機能を使用していたのですが、SupabaseのPROプランでは100,000 monthly active users then $0.00325 per MAUという料金設定になっていて、今後ユーザーが1000万人くらいになるかもしれないな(なったらいいな)と見積もっている今のサービスでは、すごい金額になってしまうなと思ったので、HonoとD1を使用した認証基盤を実装したいと思いました。また、D1を使用する理由としてはユーザーの認証情報に関しては複雑なリレーションなどを使用するわけでもないし、ユーザーAのレコードにはユーザーAしかアクセスしないので、全てのレプリカにデータが伝搬されることを待つ必要もないと思ったからです。以前にHonoとD1を使用して認証APIを実装したことはあるのですが、今回、Supabaseの認証機能から移行する際にまた新しくAPIを実装する必要があり、Better Authを使ってみることにしました。結論から言うと、すごく良いと思いました。(エンドポイントがブラックボックス化してしまう部分は少しデバッグなどの観点から嬉しくないですが、動くところまで持っていけばかなり使いやすいです。)

手順と説明

手順については日本語版のREADMEを見てもらえればわかると思うのですが、難しい点について軽く説明しようと思います。
https://github.com/shinaps/better-auth-hono-d1/blob/main/README_JP.md

better-auth:generate

このコマンドではBetter Authの認証で使用されるDDLを吐き出すことができます。
Configファイルは以下のようになっていて、APIで使用する部分と共通化できるものは共通化してしまっています。
https://github.com/shinaps/better-auth-hono-d1/blob/840e74a1193aedbfcc4d7c6736240ae130d57811/better-auth/auth.ts

APIで使用する方
https://github.com/shinaps/better-auth-hono-d1/blob/840e74a1193aedbfcc4d7c6736240ae130d57811/src/lib/auth.ts

ここでConfigファイルを分けている理由としては、DDLをdumpするときにdatabaseを指定しないといけないのですが、better-authのコマンド経由でBindingsにアクセスできるようにしたり、D1のHTTP APIを使用して接続すると、複数環境へのマイグレーションなどが難しくなるので、適当なsqliteファイルを指定して、dumpだけさせています。

https://orm.drizzle.team/docs/guides/d1-http-with-drizzle-kit

better-auth:generateによって0000_betterauth_migration.sqlというファイル名で決め打ちしてしまってるのですが、今後スキーマを変更する場合のためにこれは無くしても良いと思います。もしmigrationを差分管理したい場合は@better-auth/cli@latest migrateを実行してローカルのsqliteのスキーマとD1のスキーマに差分が出ないよう管理する必要があります。

https://www.better-auth.com/docs/concepts/cli#migrate

wrangler:migrate:*:apply

このコマンドで各種DBにマイグレーションを実行します。

HonoからこのAPIを呼び出す場合のサンプル

以下のような感じでミドルウェアを作成して認証します。
(これに関してはBindingsを使って認証APIと同じようにする方が良いと思いますが、こういう使い方もあると思うので書きました。)

app.use(async (c, next) => {
  const authClient = createAuthClient({
    baseURL: 'https://auth.example.com',
  })

  const result = await authClient.getSession({
    fetchOptions: {
      headers: c.req.raw.headers,
    },
  })

  if (!result.data) {
    return c.json({ error: 'Unauthorized' }, 401)
  }

  await next()
})

ReactからこのAPIを呼び出す場合のサンプル

以下のような感じでclientを作成します。

// src/lib/auth.ts
import { createAuthClient } from 'better-auth/react'
import { env } from '@/lib/env.ts'

export const authClient = createAuthClient({
  baseURL: 'https://auth.example.com',
})

そして以下のように使います。

// src/layouts/auth-layout.tsx
import { Navigate, Outlet } from 'react-router-dom'
import { authClient } from '@/lib/auth.ts'

export const AuthLayout = () => {
  const { data: session, isPending, error } = authClient.useSession()

  if (isPending) {
    return <div>Loading...</div>
  }

  if (error || !session) {
    return <Navigate to="/login" replace />
  }

  return <Outlet />
}

便利なリンク集

実装にあたって何回か見たページを貼っておきます。

https://www.better-auth.com/docs/concepts/cli

https://www.better-auth.com/docs/integrations/hono

https://www.better-auth.com/docs/concepts/cookies

https://www.better-auth.com/docs/reference/options

最後に

Better Authを使えばHonoとD1を使用して簡単にレートリミットが実装できるので、これもいいなと思いました。

https://www.better-auth.com/docs/concepts/rate-limit

ちなみにSupabaseは課金ユーザー向けの機能と、toB向けの機能と、postgisを使う部分で引き続き使っていきます。

追記

2025/04/06

https://www.better-auth.com/docs/reference/options#advanced
Supabaseなどの、userIdがUUIDV4形式のDBと連携したい場合は、こちらのgenerateIdというオプションを使用してid作成時にUUIDV4でidを作成することができます。

// 例
advanced: {
    generateId: () => crypto.randomUUID(),
},

告知

まだ検索などは十分ではないですが、自分が投稿したレビューに基づいてタイムラインやランキングが変化する口コミサービスを作ったのでよかったら使ってみてください。

https://repemeshi.com/

shinaps テックブログ

Discussion