🐷

Hono + Cloudflare + ClerkでAPIバックエンド作りました。【後編】

2024/12/15に公開

はじめに
2024年 GAOGAOアドベントカレンダー 16日目を担当します、SatoshiTech です!

普段はフロントエンド開発でプロジェクトに参画させていただいていますが、今回は、Hono+Cloudflare+Drizzle+ClerkでバックエンドAPIを作成した話になります。

この記事は、前回からの続きになります。
前回の記事の概要は以下の通りです。

  • 技術構成
  • Step1 DB構築
  • Step2 API構築
    • backend
    • frontend(基本コード)

今回の記事では、認証にClerkを使用したので、これに関連する、バックエンドとフロントエンド側のコードについて解説したいと思います。

Step3 Clerkの設定とFrontend

Clerkを使った理由。

まず、認証サービスとしてなぜClerkを採用したかについてまとめておきます。
個人的に一番興味をもったのが、組織(Organizations)機能(組織単位でユーザーをグループ化し、階層的な管理が可能)です。今回の実装では使用していませんが、今後是非試したい機能です。
その他、二段階認証機能や、ソーシャルログインが充実していることも良いと思います(Googleだけでなく、LINEなどもある)。
ただし、API料金は高いので、toC向けサービスだとコスト的に厳しそうです。

Clerkの設定

設定対象のアプリケーションを指定したあと、「Configure」-> 「API keys」のメニューを開く。
この画面で、Publishable key,Secret keyを取得する。

ClerkSettingImage

環境変数への登録

  • ルートディレクトリの .envに VITE_CLERK_PUBLISHABLE_KEY="xxxxxx" を登録。(xxxxは実際の値に変更)
  • ルートディレクトリの .dev.vars に CLERK_SECRET_KEY = "xxxxxxx" を登録。(xxxxは実際の値に変更)

Cloudflareダッシュボード

「Workers & Pages」から、設定対象のアプリを選択。
CLERK_PUBLISHABLE_KEYとCLERK_SECRET_KEYを登録する。

CloudflareClerkSettingImage

Frontend Code

ライブラリインストール

$ npm i @clerk/clerk-react

ClerkProviderですべてのコンポーネントをラップ。
この部分は公式ドキュメント通り。

/src/main.tsx
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.tsx'
import './index.css'

import { ClerkProvider } from '@clerk/clerk-react'

const PUBLISHABLE_KEY = import.meta.env.VITE_CLERK_PUBLISHABLE_KEY

if (!PUBLISHABLE_KEY) {
  throw new Error('Missing Publishable Key')
}

ReactDOM.createRoot(document.getElementById('root')!).render(
  <React.StrictMode>
    <ClerkProvider publishableKey={PUBLISHABLE_KEY} afterSignOutUrl="/">
      <App />
    </ClerkProvider>
  </React.StrictMode>
)

認証ルーティングの設定。すべての画面で認証必須とします。

/src/app.tsx
import { BrowserRouter, Routes, Route } from 'react-router-dom'
import { setupIonicReact } from '@ionic/react'
import '@ionic/react/css/core.css'
import '@ionic/react/css/normalize.css'
import 'bootstrap/dist/css/bootstrap.min.css'
import { SignedIn, SignedOut, RedirectToSignIn } from '@clerk/clerk-react';

import './App.css'
import { MainBoard } from './components/MainBoard'
import { History } from './components/history/History'

setupIonicReact()

const ProtectedRoute = ({ children }: { children: React.ReactNode }) => {
  return (
    <>
      <SignedIn>{children}</SignedIn>
      <SignedOut>
        <RedirectToSignIn />
      </SignedOut>
    </>
  );
};

const App = () => {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<ProtectedRoute>
              <MainBoard />
            </ProtectedRoute>} />
        <Route path="/history" element={<ProtectedRoute>
              <History />
            </ProtectedRoute>} />
      </Routes>
    </BrowserRouter>
  )
}

export default App

API通信部分。カスタムフック経由で、各コンポーネントからAPIコールする。
認証token情報をheaderに含めてサーバーと通信。

/src/hooks/useDrillsApi.ts
const fetchWithAuth = useCallback(async (url: string, options: RequestInit = {}) => {
const token = await getToken();
const defaultOptions: RequestInit = {
	mode: 'cors',
	headers: {
	'Authorization': `Bearer ${token}`,
	'Content-Type': 'application/json',
	'Accept': 'application/json'
	},
};
const response = await fetch(url, { ...defaultOptions, ...options });
if (!response.ok) {
	throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
}, [getToken]);


// drill情報の取得
const fetchDrills = useCallback(async () => {
	setIsLoading(true);
	setError(null);
try {
	const data = await fetchWithAuth(`${API_BASE_URL}/drills`);
	const convertedData = data.map(convertApiDrillToLocalFormat);
	setDrills(convertedData);
} catch (error) {
	setError('Failed to fetch drills');
	console.error('Error fetching drills:', error);
} finally {
	setIsLoading(false);
}
}, [fetchWithAuth]);


// 以下省略

認証関係のUI

Clerk標準のものを使わせていただいています。いろいろとカスタマイズもできるようです。
今回のWebアプリは、ドメインのルートにアクセスすると、Clerk標準の認証画面が起動します。

ソーシャルログイン画面(Clerk標準)
ClerkLoginImage

ナビ(Googleアカウントのアイコンが表示されます)
ClerkNavImage

プロフィール管理画面(Clerk標準)
ClerkManageAccountImage1

webhook連携

webhookを設定して、Clerkでユーザーが追加されたら、あらかじめ作成したユーザーテーブルに情報を自動追加させることも可能です。

参考)

まとめと振り返り

・様々なライブラリ、サービスを組み合わせて、フロントエンド技術の延長で、バックエンドが構築できたのはよかった。
フレームワークをいちから学ぶより学習コストは低い。個人開発のバックエンド用として今後も使っていこうと思った。
・Cloudflare D1 (sqllite) は、DBセットアップなど面倒な部分がなく、とても手軽に開発できる、かつ安価であることもよかった。
sqlliteは、まだまだ本格的な利用には難しいという意見もあるようですが、意外に使えるシーンがおおいという意見もありました。
参考:アメリカのスタートアップシーンで活躍されているEjikenさんのtweet

・上記のtweetの影響もあり、次は、tursoをつかってみたい。sqlite系は無料枠が大きいのがいい。
例えば、Postgress系のサービスも無料枠があるが、作成できるDBは2個までとか制約があるのは辛い。
・ORMはPrismaかDrizzleか迷ったが、フロントエンドライブラリで例えると、PrismaがBootstrap、DrizzleがTailwindに例えた情報をみて、それなら、Drizzleのほうがいいかなとおもって採用しました。
Drizle vs Prismaのスクラップ

ただし、その後のネットの情報をみていると、Prismaの方が流行りそうな気もしてきた。
まあ、自分はこの部分で複雑なことをする予定はないので、あまり気にしなくてもいいのではと思ってます。

・今回APIサーバーを別でデプロイしたので、今後、フロント側をネイティブアプリで構築したい場合も、スムーズに連携できると期待しています。

フロントエンドUI部分の解説記事(次回予定)

swipe sliderを使用したフロントエンドの実装については、別途記事を作成してアップしたいと思います。

swipe-slider-frontend

Discussion