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とかいうのが出たらしいし触ってみたい!
- 新しいツールどしどし触りたい!
環境
- SvelteKit + Vite + Svelte Material UI(フロント)
- Hono + Drizzle ORM(バック)
- Cloudflare D1(DB)
- Cloudflare Workers + Wrangler(インフラ)
- Bun(JSランタイム)
- Zod(バリデータ)
- Biome(コード整形、リンター)
- Lucia(認証)
やりたい事触ってみたいもの全部載せたらえらいことになってしまいました...
小中学生の頃、ヘブンズゲートとバイオレンスサンダーとアポロヌスドラゲリオンとゲキメツ入れたデッキや、サイバーエンドとレッドアイズアンデッドとアームドドラゴンのデッキを作っていたのを思い出しました。
ちなみに、規模も小さい個人プロジェクトなのでHono
かSvelteKit
どちらか+αだけで十分に開発可能です。
ただあえて「バックエンド」というものを切り分けて触ってみたかったので分業っぽい構成にしました。
なんとHono v3.11、そしてSvelteKit v2.0です(今回の内容的にあまり変わらないけど)
環境構築
まず、今回はclient
とserver
それぞれディレクトリを作成し、バックのコードをgit submoduleなどでフロントに共有していることを想定して作成します。
│
├── client/
│ └── Sveltekit
├── server/
│ └── Hono
設定
Bunをインストール
curl -fsSL https://bun.sh/install | bash
成功するとPATH
を通すコマンドを教えてくれるので、自身の環境に合わせてCLIのPATHを通します。
ちなみにBunにはoutdated
やaudit
がまだないのでnpm
も最低限使えるようにしておくとライブラリのバージョンや脆弱性チェックが楽になります。
Issueはあるのでいつか対応するかもしれませんね。
Biomeをインストール
(フロントとバックそれぞれ構築するので、それぞれでやります)
bun add --dev --exact @biomejs/biome
bunx @biomejs/biome init
VSCodeユーザーの方はついでにBiome用の設定もしてしまいましょう。もう公式日本語ドキュメントがあるなんて素晴らしいですね。
Rust製の速さはimport解析くらいでしか体感できませんでしたが、設定一本化のうえ依存ライブラリが激減するので満足しています。
たまにVSCodeがエラーを検知し続けるバグが起きるので気をつけたほうが良いかもしれません。
{
"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用にアダプタ設定
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
{
"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
を作成して記述していきます。細かい内容は公式資料などを参考にしてください。
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
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連携も共通化しておきます。
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に入れています。
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にデプロイします。
多分デフォルトでスクリプトないので作成しましょう。
"deploy": "wrangler deploy"
bun run deploy
DBのテーブル生成
Drizzleでテーブル生成と型定義を兼ねたスキーマを実装します。
Cloudflare D1の中身はsqliteなのでそれに沿って書きましょう。
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(),
});
書けたら実際にテーブル生成を行います。
"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の宣言をそのまま利用することで、カラムの型を受け取る事が可能です。
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をルーティングします。
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上でのみの利用なのでビルドに入ってなくても動いてます。(今のところ)
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独特?のリアクティブ宣言がかっこいいですね。
<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
フォームを画面に追加します
<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と同時にサーバー側で処理を走らせることが可能です。
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のフロント側サービスからサイトにアクセスしましょう。
問題なく実装できていればPOSTすることでD1のテーブルにカラムが追加されるはずです。
サーバーサイド側でのログ出力はWorkersのLogsタブからリアルタイムに確認が可能になっています。
終わりに
疲れた
なんとなく流行りものを触ろうと思い立ち、どうせならフロントとバック別作業可能な構造にしようと思ったのが苦難の始まりでした...慣れないのに挑戦しすぎたけど、まぁなかなか楽しい執筆期間でした。
これが何かの役に立つと幸いです。
書いている途中にBunもHonoも凄い速度でアプデ入るし、Sveltekitは2.0になってしまいました。
一応12/17現在最新版を入れて動いているのでご安心ください(?)
活発に開発されている最新環境に食らいつく経験は意外と楽しく、パッケージの中身を気合いで読んだり、分離されたフロント・バック間での型情報の取り回しに悩んだり、そもそも初めてバックエンドに触ったので単純に良い経験を得たなと思います。
出来上がった暁にはvitest
も入れてみたいですね、1.0出たので。
友人のサイトについては、DB設計やワイヤーフレームは出来上がっているので年明けに色々ロジック組んだりCSS入れたりして作成する予定です。
そもそも開業前なので宣伝も何もないのですが、来年あたり「おうちの執事」という名前の家事代行業が浅草あたりで始まっていたらあぁ、去年のTier1か
とでも思っていただければ幸いです。ご利用も是非。
かっけぇロゴだけ上がっているので掲載しておきます。
参考サイト
- 各公式サイト
- 各OSSのGitHub
- https://dev.classmethod.jp/articles/getting-started-cloudflare-workers-hono-cloudflare-d1
- https://zenn.dev/ryoppippi/articles/9f975b207b7f64
- https://zenn.dev/kosei28/articles/f4bac1ed2b64a7
- https://zenn.dev/mizchi/articles/d1-drizzle-orm
- https://zenn.dev/babel/articles/eslint-flat-config-for-babel
- https://reffect.co.jp/svelte/sveltekit/
- https://zenn.dev/yusukebe/articles/efa173ab4b9360
- https://zenn.dev/melodyclue/articles/70d84feb3234b8
- https://zenn.dev/moutend/articles/f9409d43724da5
- https://qiita.com/Nogi-SY/items/d423b316093ffd0eab93
- https://qiita.com/TellMin/items/25150684160759c91270
- https://qiita.com/kmkkiii/items/2b22fa53a90bf98158c0
- https://github.com/qwacko/sveltekit-lucia-starter/blob/master/src/lib/server/createUserHandler.ts
- https://github.com/gustavocadev/sveltekit-drizzle-orm-planetscale-lucia/blob/main/src/routes/login/%2Bpage.server.ts
- https://zenn.dev/gawarago/articles/f75f5113a3803d
- https://zenn.dev/ryoppippi/articles/aea8dcbc21c39e
- https://ashutosh.dev/how-to-handle-in-forms-in-svelte/
- https://zenn.dev/k_sato/books/b868c1705b8337
おまけ
Luciaの認証を時間の都合で最後まで出来ませんでした...
調べた限りのことを載せておきます。
Luciaのインストール
メールや名前、OAuthなどが利用できる認証ライブラリだそうです。
詳しい事はよくわかりませんが、DBに用意した各テーブルuser_key
がメアドやOAuthとの連携を担い、ユニークなuser_id
とそれに紐づくパスワードを、user_session
に現在の状態管理を担っているっぽいです。
bun add lucia @lucia-auth/adapter-sqlite
設定ファイルの作成
今回はEMailでログインをする想定で設定をします。
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>;
テーブルの作成
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(),
});
+++
型情報の追加
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の作成
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など何か別の方法で通信を管理する必要があります。
ここでタイムアップしました。
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とかクロスギアとかすげぇカッコよかったけどまだあるのかなぁ。