LoginSignup
92
30

【2023環境デッキ?】Bun + Hono + SvelteKit + Cloudflare D1 + Drizzle ORM

Last updated at Posted at 2023-12-21

WEB開発環境Tier1を組もうぜ?

大学からの友人に「俺は起業をするぞ!多少なら出すからWEBサイト作れん?」と言われたので あらゆる自由 を条件に(ほぼ)無償で引き受けた案件という名の趣味制作で「ぼくのかんがえた最強のWEBサイト」を目指してみました。保守運用の汎用性は犠牲となったのだ
今年もアドベントカレンダーに投稿する機会を得たので、環境構築過程とAPI接続ができるまでをご紹介します。(他の方々の記事もぜひ読んでください)

作業メモも兼ねているため中々な長さかつ読みづらいかと思いますが、必要な部分を掻い摘んで読んでいただければ幸いです。

自己紹介

  • '97年製
  • 実務歴: Angular 1年、React 4年

見ての通りフロントエンド単色染め使いなので今回人生で初めてSQLだのDBだの真面目に勉強しました。ORM使ってるけどSQLライクな記法なので勉強したと言わせてください、元々SELECTとWHEREしか知らなかったんだ...。
これから書くものは全て動作確認はしてるのですが、知見不足ゆえ説明等に誤りがあったら申し訳ないです。

作るもの

ざっくり言うと「家事代行サービス」のWEBサイトで業務ロジックは「基本情報の表示」「広告」「予約」が出来れば良いというシンプルなもの、オーバースペック気味な環境ではあるものの無償の範囲なら あらゆる自由が認可されている ので練習も兼ねてこれを開発します。
本記事では流石に全工程を書ききれないので環境作ってAPI接続するところまでを記載します。

目指すもの

  • SSR系のフロントエンドフレームワークやってみたい!
  • JSなら多少分かるからJS系のバックエンドフレームワークでAPI製造初挑戦したい!
  • 可能な限り常に型を保全したい!
  • CloudflareのD1が凄いって小耳に挟んだし使ってみたい!
  • Bunとかいうのが出たらしいし触ってみたい!
  • 新しいツールどしどし触りたい!

環境

やりたい事触ってみたいもの全部載せたらえらいことになってしまいました...
小中学生の頃、ヘブンズゲートとバイオレンスサンダーとアポロヌスドラゲリオンとゲキメツ入れたデッキや、サイバーエンドとレッドアイズアンデッドとアームドドラゴンのデッキを作っていたのを思い出しました。

ちなみに、規模も小さい個人プロジェクトなのでHonoSvelteKitどちらか+αだけで十分に開発可能です。
ただあえて「バックエンド」というものを切り分けて触ってみたかったので分業っぽい構成にしました。

なんとHono v3.11、そしてSvelteKit v2.0です(今回の内容的にあまり変わらないけど)

環境構築

まず、今回はclientserverそれぞれディレクトリを作成し、バックのコードをgit submoduleなどでフロントに共有していることを想定して作成します。

│
├── client/
│   └── Sveltekit
├── server/
│   └── Hono

設定

Bunをインストール

curl -fsSL https://bun.sh/install | bash

成功するとPATHを通すコマンドを教えてくれるので、自身の環境に合わせてCLIのPATHを通します。

ちなみにBunにはoutdatedauditがまだないのでnpmも最低限使えるようにしておくとライブラリのバージョンや脆弱性チェックが楽になります。
Issueはあるのでいつか対応するかもしれませんね。

Biomeをインストール

(フロントとバックそれぞれ構築するので、それぞれでやります)

bun add --dev --exact @biomejs/biome
bunx @biomejs/biome init

VSCodeユーザーの方はついでにBiome用の設定もしてしまいましょう。もう公式日本語ドキュメントがあるなんて素晴らしいですね。
Rust製の速さはimport解析くらいでしか体感できませんでしたが、設定一本化のうえ依存ライブラリが激減するので満足しています。
たまにVSCodeがエラーを検知し続けるバグが起きるので気をつけたほうが良いかもしれません。

settings.json
{
  "editor.defaultFormatter": "biomejs.biome",
  "editor.codeActionsOnSave": {
    "quickfix.biome": true,
    "source.organizeImports.biome": true
  }
}

ちなみに、興味があって入れたは良いもののBiomeはまだSvelte未対応なのでお気をつけください。
ReactのHooksとかJSXとかは対応しているのでぜひ利用してみてください。

フロントエンド

SvelteKitでプロジェクトの生成

bun create svelte@latest client

色々質問されるのでTypeScript使うよーとか適当に答えましょう。
Demo Appが不要ならスケルトンにしておけば開発を邪魔なものなく始められます。

cd {{Project Name}}
bun i
bun --bun run dev

--bunオプションをつけるとCLIの実行を設定に依らずBun利用にオーバーライドできるそうです。

SveltKitのcreateコマンドで入るライブラリは必ずしも最新ではないです。Stableよりも最新体験を優先するのであれば更新しておきましょう。

bun update

updateコマンドはメジャーアップデート等の大きな更新は自動でしないので、必要ならlatest版を再addなどしてください。

今回はCloudflareWorkersを利用してホスティングをする事が決定しているのでSveltのアダプタを変えておきましょう。

bun remove @sveltejs/adapter-auto
bun add @sveltejs/adapter-cloudflare-workers

これはデプロイ先の環境に合わせて切り替えるものですが、一応autoのままでも大丈夫です(多分)。
@sveltejs/adapter-cloudflare推奨というような事が公式サイトに記載ありますが、WorkersにこのアダプタでWrangler経由のデプロイをうまくやる方法がいまいち掴めませんでした。

Cloudflare用にアダプタ設定

svelte.config.js
import adapterCloudflare from '@sveltejs/adapter-cloudflare';
import { vitePreprocess } from '@sveltejs/kit/vite';

/** @type {import('@sveltejs/kit').Config} */
const config = {
	preprocess: [vitePreprocess()],

	kit: {
		adapter: adapterCloudflare({}),
		alias: {
			$features: 'src/features',
			'$features/*': 'src/features',
			$lib: 'src/lib',
			'$lib/*': 'src/lib/*',
            $server: '../server',
            '$server/*': '../server/*',
		}
	}
};

export default config;

ついでにエイリアスの設定を記述して$lib/~~などでimportが出来るようにしています。

tsconfigの設定

名前空間設定のためにライブラリを入れます。

bun add bun-types @cloudflare/workers-types
tsconfig.json
{
  "compilerOptions": {
    "paths": {
      "$server": ["../server"],
      "$server/*": ["../server/*"]
    },
    "allowJs": true,
    "checkJs": true,
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "resolveJsonModule": true,
    "skipLibCheck": true,
    "sourceMap": true,
    "strict": true,
    "moduleResolution": "bundler",
    "noEmit": true,
    "allowImportingTsExtensions": true,
    "moduleDetection": "force",
    "jsx": "react-jsx",
    "types": ["@cloudflare/workers-types", "bun-types"]
  },
  "extends": "./.svelte-kit/tsconfig.json"
}

SvelteKitはts-configに別の独自設定をオーバーライドしているので要チェックです。このオーバーライド用のコンフィグファイルはビルドごとにsvelteコンフィグなどから再生成されます。
そのためextendsのファイルがない場合、一度ビルドすると生成されます。そんなファイルないよとエラーが出る場合お試しください。

bun run build

インフラ

Cloudflareの設定

Cloudflareにアカウントを作りダッシュボードへ行きます。

Workers & Pagesのタブを開き、アプリケーションの作成からワーカーを作ります。
今回はフロントとバックにそれぞれ一つずつ作成します。

D1のタブを開いてデータベースを作成します。

Wranglerのインストール

WranglerというCloudflare Workersの開発ツールを入れ、Cloudflareと連携させます。

bun add wrangler @cloudflare/wrangler @cloudflare/workers-types
bunx wrangler login

ちなみに今回は使いませんがbunx wrangler init -yでプロジェクトを簡単に構築してくれます。

プロジェクトの設定

アカウントIDなどの情報をコピーし、プロジェクトのルートディレクトリにwrangler.tomlを作成して記述していきます。細かい内容は公式資料などを参考にしてください。

wrangler.toml
name = {{フロント用Workers名}}
account_id = {{Workers&Pagesで見れるアカウントID}}
compatibility_flags = []
workers_dev = true
compatibility_date = "2023-12-07"
main = "./.cloudflare/worker.js"
site.bucket = "./.cloudflare/public"

services = [
  { binding = {{バック用Workers名}}, service = {{バック用Workers名}}}
]

build.command = "bun run build"

同じドメイン間・サブドメイン間のWorkers同士はfetch経由などで通信できないため、service bindingsという機能を利用して通信を行います。
直接Workersが閉じたネットワークで通信するため、速度の向上も見込めます。

再度ビルドをするとデプロイ用の.cloudflareディレクトリが生成されます。

bun run build

ローカル環境でD1などのテストをする場合はminiflareを入れておきましょう。
今回は作業時間の都合、検証環境をCloudflare上に作って触っていたため割愛しました。

bun add miniflare

バックエンド

Honoのインストール

cd ../
bunx create-hono

プロジェクト名をserver、Templateはcloudflare-workersにします。
ついでにzodとZod用のミドルウェアも入れておきます。

cd server
bun add hono @hono/zod-validator zod

wranglerの設定

バックエンド側も設定を入れていきます。
DB設定がこちらでは入ります。

bunx wrangler login
wrangler.toml
name = {{バック用Workers名}}
account_id = {{Workers&Pagesで見れるアカウントID}}
compatibility_flags = []
workers_dev = true
compatibility_date = {{互換性を持たせる基準日}}

[[d1_databases]]
binding = "DB" # i.e. available in your Worker on env.DB
database_name = {{データベース名}}
database_id = {{データベースID}}

Drizzle ORMのインストール

Drizzle ORMとそのCLI、DB操作を簡単にしてさらに型安全にする。

bun add drizzle-orm drizzle-kit

接続準備

Hono側

型定義を作成します。
ついでにDrizzleの設定とAPIルーティング設定も用意します。
APIContextが各APIに渡る情報の型で、D1DatabseがDB側の型設定になります。
また、drizzleのDB連携も共通化しておきます。

server/src/index.ts
import { Context, Hono } from "hono";
import { drizzle } from "drizzle-orm/d1";

type Bindings = {
  DB: D1Database;
};

export type APIContext = Context<
  {
    Bindings: Bindings;
  },
  "/",
  {}
>;

export const dbClient = (db: D1Database | undefined) => {
  if (!db) {
    throw new Error("Database not found");
  }
  console.log("dbClient");
  return drizzle(db);
};

const app = new Hono<{ Bindings: Bindings }>();

app
  .onError((err, ctx) => {
    console.error(err);
    return ctx.json({ error: "Internal Server Error" }, 500);
  });

  .get("/", (c) => c.text("Hello Hono!"));

export default app;

honoのコードをCloudflareにデプロイします。

bun run deploy

Sveltekit側

Cloudflareの提供する環境変数などにアクセスできるように型を渡す。
ここにどこまで追加が必要なのかは環境によるのでご自身の環境に合わせて設定してください。
Service Bindings経由で通信をするため、その設定をenvに入れています。

client/src/app.d.ts
declare global {
	namespace App {
		// interface Error {}
		// interface PageData {}
		interface Platform {
			context?: {
				waitUntil(promise: Promise<unknown>): void;
				passThroughOnException: () => void;
			};
			env: {
				// YOUR_KV_NAMESPACE: KVNamespace;
				// YOUR_DURABLE_OBJECT_NAMESPACE: DurableObjectNamespace;
				DB: D1Database;
                {{バック用Workers名}}: Fetcher;
			};
			caches?: CacheStorage & { default: Cache };
		}
	}
}

export {};

SveltekitのコードをCloudflareにデプロイします。
多分デフォルトでスクリプトないので作成しましょう。

package.json
"deploy": "wrangler deploy"
bun run deploy

DBのテーブル生成

Drizzleでテーブル生成と型定義を兼ねたスキーマを実装します。
Cloudflare D1の中身はsqliteなのでそれに沿って書きましょう。

server/src/DB/schema.ts
import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core";

export const test = sqliteTable("Test", {
  id: integer("id").primaryKey({ autoIncrement: true }).notNull(),
  name: text("name").notNull(),
});

書けたら実際にテーブル生成を行います。

server/package.json
 "migrate": "drizzle-kit generate:sqlite --out migrations --schema src/DB/schema.ts",
 "migrate:apply": "wrangler d1 migrations apply {{D1のデータベース名}}"

まずはmigrateを行います。そうすると.wranglerディレクトリが作成されます。
そのあとapplyを行うことで、実際にD1上にテーブルが作成されます。

GETのテスト用にカラムを追加しておきましょう。

API接続

バックエンド

APIを実装します。
Drzzleを利用してSQLライクなメソッドを繋いでいきます。
envからwrangler.tomlで指定したDB情報を取得できます。
Schemaの宣言をそのまま利用することで、カラムの型を受け取る事が可能です。

server/src/ORM/test.ts
import { eq } from "drizzle-orm";
import { APIContext, dbClient } from "..";
import { test } from "../DB/schema";

export const getAllTest = async (c: APIContext) => {
  const result = await dbClient(c.env.DB).select().from(test).all();
  return c.json(result);
}

export const createTest = async (c: APIContext) => {
  const formData = await c.req.formData();
  const request: typeof test.$inferInsert = {
    name: formData.get("name")?.toString() ?? "",
  };
  
  // Validation
  
  await dbClient(c.env.DB).insert(test).values(request);
  return c.json(result);
};

次に作ったAPIをルーティングします。

server/sec/index.ts
import { Context, Hono } from "hono";
import { drizzle } from "drizzle-orm/d1";
+ import { getAllTest, createTest } from "./ORM/test";

type Bindings = {
  DB: D1Database;
};

export type APIContext = Context<
  {
    Bindings: Bindings;
  },
  "/",
  {}
>;

export const dbClient = (db: D1Database | undefined) => {
  if (!db) {
    throw new Error("Database not found");
  }
  console.log("dbClient");
  return drizzle(db);
};

const app = new Hono<{ Bindings: Bindings }>();

app
  .onError((err, ctx) => {
    console.error(err);
    return ctx.json({ error: "Internal Server Error" }, 500);
  });

- .get("/", (c) => c.text("Hello Hono!"));
+ .get("/", getAllTest)
+ .post("/", createTest)

export default app;

ここまでできたら再デプロイをします。

bun run deploy

フロントエンド

フロントからAPIを呼び出す部分を作ります。
+page.server.tsはサーバー上で処理されるロジックを入れるファイルで、PageServerLoadはページの読み込み開始時に動かすコードを記載できます。実動作はともかく感覚はReactのSuspenceのように事前にやりたい処理を入れる感じです。今回はAPIからデータをFetchさせます。
PageServerLoad$typesからimportしていますが、これはコードをビルドすると自動で生成されます。そのため一通りコードを書いたらbun run buildで型生成をしましょう。
また、スキーマからの型定義だけserverから引っ張っています。コンパイル前のTypeScript上でのみの利用なのでビルドに入ってなくても動いてます。(今のところ)

client/src/routes/+page.server.ts
import type { test } from "$server/src/DB/schema";
import type { PageServerLoad } from "./$types";

export const load = (async ({ request, platform }) => {
  const res = await platform?.env.{{バック用Workers名}}.fetch(request);
  const result = await res?.json<(typeof test.$inferSelect)[]>();

  return result;
}) satisfies PageServerLoad;

GETしてきた値を利用します。
ここで使うPageServerDataも上記と同じく自動生成なのでエラーになる場合はビルドをしましょう。
重要なのはlet dataをexportする事です。こうしないとserver側からのパラメータを取得できません。
svlete独特?のリアクティブ宣言がかっこいいですね。

client/src/routes/+page.svelte
<script lang="ts">
  import type { PageServerData } from "./$types";

  export let data: PageServerData;

  $: console.log(data[0]?.id);
</script>

<h1>Welcome to SvelteKit</h1>
<p>
  {data[0]?.id ? data[0]?.id + data[0]?.name : ""}
</p>

POSTのためにフォームを追加します。
まずはSMUIから必要なパーツを選びaddします。
面倒ですが1パーツずつ入れていくらしいです。

bun add @smui/button @smui/textfield

フォームを画面に追加します

client/src/routes/+page.svelte
<script lang="ts">
  import type { PageServerData } from "./$types";
// ここから追加部分
  import { enhance } from "$app/forms";
  import Button, { Label } from "@smui/button";
  import TextField from "@smui/textfield";
// ここまで追加部分

  export let data: PageServerData;

// ここから追加部分
  let question: string | null = null;
  let answer: string | null = null;
  let email: string | null = null;
  let name: string | null = null;
// ここまで追加部分

  $: console.log(data[0]?.id);
</script>

<h1>Welcome to SvelteKit</h1>
<p>
  {data[0]?.id ? data[0]?.id + data[0]?.name : ""}
</p>

<!-- ここから追加部分 -->
<form
  method="post"
  use:enhance={() => {
    return async () => {};
  }}
>
  <TextField
    type="text"
    style="width: 100%;"
    required
    bind:value={question}
    input$name="question"
  />
  <TextField
    type="text"
    style="width: 100%;"
    required
    bind:value={answer}
    input$name="answer"
  />
  <TextField
    type="text"
    style="width: 100%;"
    required
    bind:value={email}
    input$name="email"
  />
  <TextField
    type="text"
    style="width: 100%;"
    required
    bind:value={name}
    input$name="name"
  />
  <Button style="width: 200px;" variant="raised">
    <Label>送信</Label>
  </Button>
</form>
<!-- ここまで追加部分 -->

formタグにmethodを追加してbuttonタグをsubmitタイプにすると自動的に送信ができます。
JavaScriptが有効な環境で実行する場合はsubmitにより不要な画面遷移が発生しますが、use:enhanceを利用することでその処理をカットして別の処理に置き換えることができますevent.preventDefault()しなくて良くなるので綺麗で良いですね。
今回は他に処理を用意していないので空白にしています。

続いて通信処理を作成します。
Form ActionsというSveltKitの機能を利用することで、formのsbmitと同時にサーバー側で処理を走らせることが可能です

client/src/routes/+page.server.ts
import type { test } from "$server/src/DB/schema";
import type { PageServerLoad } from "./$types";

export const load = (async ({ request, platform }) => {
  const res = await platform?.env.{{バック用Workers名}}.fetch(request);
  const result = await res?.json<(typeof test.$inferSelect)[]>();

  return result;
}) satisfies PageServerLoad;

+++
export const actions = {
  default: async ({ platform, request }) => {
    // Validation

    await platform?.env.api.fetch(request.clone());

    return { success: true };
  },
} satisfies Actions;
+++

ここまでできたら再デプロイをします。

bun run deploy

画面確認

これでようやく、可能な限り型安全な状態で値を画面に表示できます。
Cloudflare Workersのフロント側サービスからサイトにアクセスしましょう。

スクリーンショット 2023-12-22 0.43.51.png

問題なく実装できていればPOSTすることでD1のテーブルにカラムが追加されるはずです。
サーバーサイド側でのログ出力はWorkersのLogsタブからリアルタイムに確認が可能になっています。

終わりに

疲れた
なんとなく流行りものを触ろうと思い立ち、どうせならフロントとバック別作業可能な構造にしようと思ったのが苦難の始まりでした...慣れないのに挑戦しすぎたけど、まぁなかなか楽しい執筆期間でした。
これが何かの役に立つと幸いです。

書いている途中にBunもHonoも凄い速度でアプデ入るし、Sveltekitは2.0になってしまいました
一応12/17現在最新版を入れて動いているのでご安心ください(?)
活発に開発されている最新環境に食らいつく経験は意外と楽しく、パッケージの中身を気合いで読んだり、分離されたフロント・バック間での型情報の取り回しに悩んだり、そもそも初めてバックエンドに触ったので単純に良い経験を得たなと思います。
出来上がった暁にはvitestも入れてみたいですね、1.0出たので。

友人のサイトについては、DB設計やワイヤーフレームは出来上がっているので年明けに色々ロジック組んだりCSS入れたりして作成する予定です。
そもそも開業前なので宣伝も何もないのですが、来年あたり「おうちの執事」という名前の家事代行業が浅草あたりで始まっていたらあぁ、去年のTier1かとでも思っていただければ幸いです。ご利用も是非。
かっけぇロゴだけ上がっているので掲載しておきます。
logo1 .png

参考サイト

おまけ

Luciaの認証を時間の都合で最後まで出来ませんでした...
調べた限りのことを載せておきます。

Luciaのインストール

メールや名前、OAuthなどが利用できる認証ライブラリだそうです。
詳しい事はよくわかりませんが、DBに用意した各テーブルuser_keyがメアドやOAuthとの連携を担い、ユニークなuser_idとそれに紐づくパスワードを、user_sessionに現在の状態管理を担っているっぽいです。

bun add lucia @lucia-auth/adapter-sqlite

設定ファイルの作成

今回はEMailでログインをする想定で設定をします。

server/src/auth/lucia.ts
import { d1 } from "@lucia-auth/adapter-sqlite";
import { lucia } from "lucia";
import { hono } from "lucia/middleware";

export const initializeLucia = (db: D1Database) => {
  const auth = lucia({
    adapter: d1(db, {
      user: "user",
      key: "user_key",
      session: "user_session",
    }),
    env: "PROD",
    middleware: hono(),
    getUserAttributes: (data) => {
      return {
        id: data.id,
        email: data.email,
      };
    },
  });
  return auth;
};

export type Auth = ReturnType<typeof initializeLucia>;

テーブルの作成

server/src/DB/schema.ts
import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core";

export const test = sqliteTable("Test", {
  id: integer("id").primaryKey({ autoIncrement: true }).notNull(),
  name: text("name").notNull(),
});

+++
export const user = sqliteTable("user", {
  id: text("id").notNull(),
});

export const user_key = sqliteTable("user_key", {
  id: text("id").notNull(),
  hashed_password: text("id").notNull(),
  user_id: text("user_id")
    .references(() => user.id)
    .notNull(),
});

export const user_session = sqliteTable("user_session", {
  id: text("id").notNull(),
  user_id: text("user_id")
    .references(() => user.id)
    .notNull(),
  active_expires: integer("active_expires").notNull(),
  idle_expires: integer("idle_expires").notNull(),
});
+++

型情報の追加

client/src/app.d.ts
declare global {
	namespace App {
		// interface Error {}
		// interface PageData {}
		interface Platform {
			context?: {
				waitUntil(promise: Promise<unknown>): void;
				passThroughOnException: () => void;
			};
			env: {
				// YOUR_KV_NAMESPACE: KVNamespace;
				// YOUR_DURABLE_OBJECT_NAMESPACE: DurableObjectNamespace;
				DB: D1Database;
                {{バック用Workers名}}: Fetcher;
			};
			caches?: CacheStorage & { default: Cache };
+			locals?: {
+    			auth: import("lucia").AuthRequest;
+    			session: import("lucia").Session | null;
+    		};
		}
	}
}

+++
declare global {
	namespace Lucia {
		type Auth = import("$server/src/auth/lucia").Auth;
		type DatabaseUserAttributes = { email: string };
		type DatabaseSessionAttributes = object;
	}
}
+++

export {};

APIの作成

server/ORM/auth.ts
import { APIContext } from "..";
import { initializeLucia } from "../auth/lucia";

export const login = async (c: APIContext) => {
  try {
    const auth = initializeLucia(c.env.DB);

    const formData = await c.req.formData();
    const email = formData.get("email")?.toString() ?? "";
    const password = formData.get("password")?.toString() ?? "";

    // Validation

    const key = await auth.useKey("email", email, password);

    const session = await auth.createSession({
      userId: key.userId,
      attributes: { createdAt: new Date() },
    });

    const authRequest = auth.handleRequest(c);
    authRequest.setSession(session);

    return c.json(session);
    }
    console.error("err");
  } catch (e) {
    console.error(e);
  }
};

export const logout = async (c: APIContext) => {
  try {
    const auth = initializeLucia(c.env.DB);

    const authRequest = auth.handleRequest(c);
    const session = await authRequest.validate();
    if (!session) {
      return c.text("Unauthorized", 401);
    }
    await auth.invalidateSession(session.sessionId);

    authRequest.setSession(null);
  } catch (e) {
    console.error(e);
    throw e;
  }
};

export const register = async (c: APIContext) => {
  try {
    const auth = initializeLucia(c.env.DB);

    const formData = await c.req.formData();
    const email = formData.get("email")?.toString() ?? "";
    const password = formData.get("password")?.toString() ?? "";

    // Validation

    const res = await auth.createUser({
      key: {
        providerId: "email",
        providerUserId: email,
        password: password,
      },
      attributes: {
        email: email,
      },
    });

    return res;
  } catch (e) {
    console.error(e);
  }
};

認証チェック&API接続

一応こんな感じで認証を監視できるらしいですが、initializeLuciaをこんな簡単に持ってこれない(サーバー側のコードだからSvelteのビルドに含まれない)のでfetchなど何か別の方法で通信を管理する必要があります。
ここでタイムアップしました。

client/src/hooks.server.ts
import { initializeLucia } from "$server/src/auth/lucia";
import type { Handle } from "@sveltejs/kit";
import { sequence } from "@sveltejs/kit/hooks";

const authHandler: Handle = async ({ event, resolve }) => {
	if (event.url.pathname.startsWith("/api/auth")) {
		console.log("auth-hook");
		// biome-ignore lint/style/noNonNullAssertion: <explanation>
		event.locals.auth = initializeLucia(event.platform?.env.DB!).handleRequest(
			event,
		);
		event.locals.session = await event.locals.auth.validate();

		const user = await event.locals.auth.validate();

		if (event.route.id?.startsWith("/(back)") && !user) {
			return Response.redirect(`${event.url.origin}/login`, 302);
		}

		if (event.route.id?.startsWith("/(back)") && user) {
			return Response.redirect(`${event.url.origin}/user`, 302);
		}
		return await resolve(event);
	}

	return await resolve(event);
};

export const handle = sequence(authHandler);

終わり次第書き足しておきます。(12/21)


自分の中で遊戯王はアンデッドワールドくらいで止まっているし、デュエマは5神の無限リンクとかゲート呪文?か何かで出てくる両面カードで止まっているんですが、なぜか無性にまたやりたくなって来ました。
M・HEROとかクロスギアとかすげぇカッコよかったけどまだあるのかなぁ。

92
30
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
92
30