このガイドは、Bun・TypeScript・SvelteKit・Prisma・Auth.js・Tailwind CSS を使ってシンプルなアカウント管理機能付きのWebアプリを作る方法を、高校生でもわかるように説明しています。最新バージョン(2025年11月時点)の情報を使用しています。

各ツールの役割:

  • Bun … JavaScriptを実行するための高速ランタイム(Node.jsの置き換え)
  • TypeScript … 型を付けたJavaScript(間違いを減らせる)
  • SvelteKit … Webサイトを作るフレームワーク
  • Prisma … データベースを簡単に操作するORM
  • Auth.js … ユーザーのログイン・ログアウトを管理
  • Tailwind CSS … CSSを簡単に書くためのツール

第1章:プロジェクトの作成と初期セットアップ

1-1. プロジェクトの作成

まず、ターミナル(コマンドプロンプト)で以下を実行します:

Terminal window
bunx sv create my-app

対話形式で質問されるので、以下のように選択してください:

◇ Which template would you like?
│ SvelteKit minimal
◇ Add type checking with Typescript?
│ Yes, using Typescript syntax
◇ What would you like to add to your project?
│ tailwind, prettier, eslint
◇ Which package manager do you want to install dependencies with?
│ bun

1-2. プロジェクトフォルダへ移動

Terminal window
cd my-app

1-3. Bun開発サーバーの起動確認

Terminal window
bun run dev

ブラウザで http://localhost:5173 を開いて、SvelteKitのデモページが表示されたら成功です。


第2章:データベースのセットアップ

2-1. PostgreSQL データベースの準備

開発用には SQLite を使う方法もありますが、本ガイドではシンプルな SQLite を推奨します。

Prismaはファイルベースの SQLite をデフォルトで使えるため、複雑な設定は不要です。

2-2. Prisma のインストール

Terminal window
bun add @prisma/client
bun add -D prisma

2-3. Prisma の初期化

Terminal window
bunx prisma init

このコマンドにより、以下のファイルが作成されます:

  • prisma/schema.prisma … データベースの構造を定義
  • .env … 環境変数(パスワードなど秘密の情報)

2-4. .env ファイルの設定

.env ファイルを編集して、データベース接続文字列を設定します:

DATABASE_URL="file:./dev.db"

SQLite をファイルベースで使う場合は、この設定で十分です。

2-5. Prisma スキーマの作成

prisma/schema.prisma を以下のように編集します:

prisma/schema.prisma
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "sqlite"
url = env("DATABASE_URL")
}
model User {
id String @id @default(cuid())
email String @unique
name String?
password String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model Session {
id String @id @default(cuid())
userId String
expiresAt DateTime
createdAt DateTime @default(now())
}
model Account {
id String @id @default(cuid())
userId String
type String
provider String
providerAccountId String
refreshToken String?
accessToken String?
expiresAt Int?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([provider, providerAccountId])
}

説明:

  • User … ユーザー情報(メール、名前、パスワード)
  • Session … ログイン状態の管理
  • Account … OAuth(Google、GitHubなど)の情報を保存

2-6. Prisma 初期マイグレーション

Terminal window
bunx prisma migrate dev --name init

このコマンドにより、SQLite ファイルが作成され、テーブルが生成されます。


第3章:Auth.js のセットアップ

3-1. 必要なパッケージをインストール

Terminal window
bun add @auth/sveltekit @auth/prisma-adapter
bun add -D @types/node

3-2. AUTH_SECRET の生成

認証に必要なシークレットキーを生成します:

Terminal window
# UNIX系(Mac、Linux)
openssl rand -hex 32

Windows の場合: https://generate-secret.vercel.app/32 にアクセスして生成してください。

生成されたキーを .env に追加します:

DATABASE_URL="file:./dev.db"
AUTH_SECRET="ここに生成されたキーをコピー"
AUTH_TRUST_HOST="true"

3-3. Auth.js の設定ファイルを作成

src/auth.ts を新規作成して、以下のコードを記述します:

src/auth.ts
import { SvelteKitAuth } from "@auth/sveltekit";
import { PrismaAdapter } from "@auth/prisma-adapter";
import Credentials from "@auth/sveltekit/providers/credentials";
import { prisma } from "$lib/server/db";
import { compare } from "bcrypt";
import type { SvelteKitAuthConfig } from "@auth/sveltekit";
async function authorize(credentials: any) {
if (!credentials?.email || !credentials?.password) {
throw new Error("メールとパスワードを入力してください");
}
const user = await prisma.user.findUnique({
where: { email: credentials.email },
});
if (!user) {
throw new Error("ユーザーが見つかりません");
}
const isPasswordValid = await compare(credentials.password, user.password);
if (!isPasswordValid) {
throw new Error("パスワードが間違っています");
}
return {
id: user.id,
email: user.email,
name: user.name,
};
}
export const { handle, signIn, signOut } = SvelteKitAuth(async (event) => {
const authOptions: SvelteKitAuthConfig = {
adapter: PrismaAdapter(prisma),
providers: [
Credentials({
credentials: {
email: { label: "メールアドレス" },
password: { label: "パスワード", type: "password" },
},
authorize,
}),
],
pages: {
signIn: "/login",
},
callbacks: {
async jwt({ token, user }) {
if (user) {
token.id = user.id;
}
return token;
},
async session({ session, token }) {
if (session.user) {
session.user.id = token.id as string;
}
return session;
},
},
};
return authOptions;
});

3-4. サーバーフック(ミドルウェア)の設定

src/hooks.server.ts を新規作成して、以下のコードを記述します:

src/hooks.server.ts
export { handle } from "./auth";

3-5. Prisma クライアントの初期化

src/lib/server/db.ts を新規作成して、以下のコードを記述します:

src/lib/server/db.ts
import { PrismaClient } from "@prisma/client";
const prismaClientSingleton = () => {
return new PrismaClient();
};
type PrismaClientSingleton = ReturnType<typeof prismaClientSingleton>;
const globalForPrisma = globalThis as unknown as {
prisma: PrismaClientSingleton | undefined;
};
export const prisma = globalForPrisma.prisma ?? prismaClientSingleton();
if (process.env.NODE_ENV !== "production") globalForPrisma.prisma = prisma;

3-6. セッション型定義

src/app.d.ts を編集して、以下を追加します:

src/app.d.ts
import type { DefaultSession } from "@auth/sveltekit";
declare module "@auth/sveltekit" {
interface Session {
user?: DefaultSession["user"] & {
id: string;
};
}
}
declare global {
namespace App {
interface Locals {
auth: import("@auth/sveltekit").Auth;
}
}
}
export {};

第4章:Tailwind CSS の設定

4-1. Tailwind は既にインストール済み

プロジェクト作成時に Tailwind を選択したので、必要なパッケージは既にインストールされています。

4-2. vite.config.ts の確認

vite.config.ts が以下のようになっているか確認してください:

vite.config.ts
import { sveltekit } from "@sveltejs/kit/vite";
import { defineConfig } from "vite";
import tailwindcss from "@tailwindcss/vite";
export default defineConfig({
plugins: [sveltekit(), tailwindcss()],
});

4-3. src/app.css の設定

src/app.css ファイルを作成・編集して、以下を記述します:

src/app.css
@import "tailwindcss";

Tailwind v4 では @import "tailwindcss" だけで全ての Tailwind スタイルが読み込まれます。

4-4. src/routes/+layout.svelte で CSS をインポート

src/routes/+layout.svelte を作成・編集して、以下を記述します:

src/routes/+layout.svelte
<script>
import "../app.css"
</script>
<slot />

第5章:パスワード暗号化(bcrypt)のセットアップ

ユーザーのパスワードは平文で保存してはいけません。必ず暗号化します。

5-1. bcrypt パッケージをインストール

Terminal window
bun add bcrypt
bun add -D @types/bcrypt

5-2. パスワード暗号化・検証ヘルパー関数

src/lib/server/password.ts を新規作成して、以下を記述します:

src/lib/server/password.ts
import { hash, compare } from "bcrypt";
const SALT_ROUNDS = 10;
export async function hashPassword(password: string): Promise<string> {
return await hash(password, SALT_ROUNDS);
}
export async function verifyPassword(
password: string,
hashedPassword: string,
): Promise<boolean> {
return await compare(password, hashedPassword);
}

第6章:ページとルートの作成

6-1. ログインページ

src/routes/login/+page.svelte を新規作成して、以下を記述します:

src/routes/login/+page.svelte
<script lang="ts">
import { signIn } from "../../auth"
import type { ActionData } from "./$types"
export let form: ActionData
let email = ""
let password = ""
</script>
<div class="flex items-center justify-center min-h-screen bg-gray-100">
<div class="bg-white p-8 rounded shadow-md w-96">
<h1 class="text-2xl font-bold mb-6">ログイン</h1>
<form method="POST" action="?/login">
<div class="mb-4">
<label for="email" class="block text-sm font-medium">
メールアドレス
</label>
<input
type="email"
name="email"
id="email"
bind:value={email}
class="mt-1 w-full px-3 py-2 border border-gray-300 rounded"
required
/>
</div>
<div class="mb-4">
<label for="password" class="block text-sm font-medium">
パスワード
</label>
<input
type="password"
name="password"
id="password"
bind:value={password}
class="mt-1 w-full px-3 py-2 border border-gray-300 rounded"
required
/>
</div>
{#if form?.error}
<div class="mb-4 text-red-600 text-sm">{form.error}</div>
{/if}
<button
type="submit"
class="w-full bg-blue-500 text-white py-2 rounded hover:bg-blue-600"
>
ログイン
</button>
</form>
<p class="mt-4 text-sm text-center">
アカウントをお持ちでないですか?
<a href="/register" class="text-blue-500 hover:underline">
サインアップ
</a>
</p>
</div>
</div>

6-2. ログインアクション

src/routes/login/+page.server.ts を新規作成して、以下を記述します:

src/routes/login/+page.server.ts
import { signIn } from "../../auth";
import { redirect } from "@sveltejs/kit";
import type { Actions } from "./$types";
export const actions: Actions = {
login: async (event) => {
try {
await signIn("credentials", {
email: event.request.formData().then((d) => d.get("email")),
password: event.request.formData().then((d) => d.get("password")),
redirect: false,
});
redirect(303, "/dashboard");
} catch (error) {
return {
error:
"ログインに失敗しました。メールアドレスとパスワードを確認してください。",
};
}
},
};

6-3. サインアップページ

src/routes/register/+page.svelte を新規作成して、以下を記述します:

src/routes/register/+page.svelte
<script lang="ts">
import type { ActionData } from "./$types"
export let form: ActionData
let email = ""
let password = ""
let passwordConfirm = ""
let name = ""
</script>
<div class="flex items-center justify-center min-h-screen bg-gray-100">
<div class="bg-white p-8 rounded shadow-md w-96">
<h1 class="text-2xl font-bold mb-6">サインアップ</h1>
<form method="POST" action="?/register">
<div class="mb-4">
<label for="name" class="block text-sm font-medium">
ユーザー名
</label>
<input
type="text"
name="name"
id="name"
bind:value={name}
class="mt-1 w-full px-3 py-2 border border-gray-300 rounded"
required
/>
</div>
<div class="mb-4">
<label for="email" class="block text-sm font-medium">
メールアドレス
</label>
<input
type="email"
name="email"
id="email"
bind:value={email}
class="mt-1 w-full px-3 py-2 border border-gray-300 rounded"
required
/>
</div>
<div class="mb-4">
<label for="password" class="block text-sm font-medium">
パスワード
</label>
<input
type="password"
name="password"
id="password"
bind:value={password}
class="mt-1 w-full px-3 py-2 border border-gray-300 rounded"
required
/>
</div>
<div class="mb-4">
<label for="passwordConfirm" class="block text-sm font-medium">
パスワード(確認)
</label>
<input
type="password"
name="passwordConfirm"
id="passwordConfirm"
bind:value={passwordConfirm}
class="mt-1 w-full px-3 py-2 border border-gray-300 rounded"
required
/>
</div>
{#if form?.error}
<div class="mb-4 text-red-600 text-sm">{form.error}</div>
{/if}
<button
type="submit"
class="w-full bg-green-500 text-white py-2 rounded hover:bg-green-600"
>
サインアップ
</button>
</form>
<p class="mt-4 text-sm text-center">
既にアカウントをお持ちですか?
<a href="/login" class="text-blue-500 hover:underline">
ログイン
</a>
</p>
</div>
</div>

6-4. サインアップアクション

src/routes/register/+page.server.ts を新規作成して、以下を記述します:

src/routes/register/+page.server.ts
import { prisma } from "$lib/server/db";
import { hashPassword } from "$lib/server/password";
import { redirect } from "@sveltejs/kit";
import type { Actions } from "./$types";
export const actions: Actions = {
register: async ({ request }) => {
const formData = await request.formData();
const email = formData.get("email") as string;
const password = formData.get("password") as string;
const passwordConfirm = formData.get("passwordConfirm") as string;
const name = formData.get("name") as string;
// バリデーション
if (!email || !password || !passwordConfirm || !name) {
return { error: "全ての項目を入力してください" };
}
if (password !== passwordConfirm) {
return { error: "パスワードが一致していません" };
}
if (password.length < 8) {
return { error: "パスワードは8文字以上である必要があります" };
}
// メールアドレスが既に登録されているか確認
const existingUser = await prisma.user.findUnique({
where: { email },
});
if (existingUser) {
return { error: "このメールアドレスは既に登録されています" };
}
// パスワードをハッシュ化
const hashedPassword = await hashPassword(password);
// ユーザーを作成
try {
await prisma.user.create({
data: {
email,
password: hashedPassword,
name,
},
});
redirect(303, "/login");
} catch (error) {
return { error: "ユーザーの作成に失敗しました" };
}
},
};

6-5. ダッシュボードページ(保護されたページ)

src/routes/dashboard/+page.svelte を新規作成して、以下を記述します:

src/routes/dashboard/+page.svelte
<script lang="ts">
import { signOut } from "../../auth"
import type { PageData } from "./$types"
export let data: PageData
</script>
<div class="min-h-screen bg-gray-100 p-6">
<div class="max-w-4xl mx-auto bg-white rounded shadow-md p-8">
<h1 class="text-3xl font-bold mb-4">
ようこそ、{data.session?.user?.name}さん
</h1>
<div class="mb-6">
<p class="text-gray-600">
メールアドレス: {data.session?.user?.email}
</p>
</div>
<form method="POST" action="?/logout">
<button
type="submit"
class="bg-red-500 text-white px-4 py-2 rounded hover:bg-red-600"
>
ログアウト
</button>
</form>
</div>
</div>

6-6. ダッシュボードサーバーアクション

src/routes/dashboard/+page.server.ts を新規作成して、以下を記述します:

src/routes/dashboard/+page.server.ts
import { redirect } from "@sveltejs/kit";
import { signOut } from "../../auth";
import type { PageServerLoad, Actions } from "./$types";
export const load: PageServerLoad = async (event) => {
const session = await event.locals.auth();
if (!session?.user) {
redirect(303, "/login");
}
return {
session,
};
};
export const actions: Actions = {
logout: async (event) => {
await signOut({ redirect: false });
redirect(303, "/login");
},
};

6-7. ホームページ

src/routes/+page.svelte を編集して、以下を記述します:

src/routes/+page.svelte
<script lang="ts">
import type { PageData } from "./$types"
export let data: PageData
</script>
<div class="min-h-screen bg-gradient-to-br from-blue-500 to-purple-600 flex items-center justify-center">
<div class="text-center text-white">
<h1 class="text-5xl font-bold mb-4">ようこそ!</h1>
<p class="text-xl mb-8">シンプルなアカウント管理アプリ</p>
{#if data.session?.user}
<a
href="/dashboard"
class="bg-white text-blue-600 px-6 py-3 rounded-lg font-semibold hover:bg-gray-200"
>
ダッシュボードへ
</a>
{:else}
<div class="space-x-4">
<a
href="/login"
class="inline-block bg-white text-blue-600 px-6 py-3 rounded-lg font-semibold hover:bg-gray-200"
>
ログイン
</a>
<a
href="/register"
class="inline-block bg-transparent border-2 border-white text-white px-6 py-3 rounded-lg font-semibold hover:bg-white hover:text-blue-600"
>
サインアップ
</a>
</div>
{/if}
</div>
</div>

6-8. ホームページサーバーロード

src/routes/+page.server.ts を新規作成して、以下を記述します:

src/routes/+page.server.ts
import type { PageServerLoad } from "./$types";
export const load: PageServerLoad = async (event) => {
const session = await event.locals.auth();
return {
session,
};
};

第7章:コードの矛盾チェック

チェックリスト

✅ 1. 環境変数の確認

.env ファイルに以下が存在するか確認:

  • DATABASE_URL … SQLite ファイルへのパス
  • AUTH_SECRET … 32文字以上のランダム文字列
  • AUTH_TRUST_HOST="true"

✅ 2. Prisma スキーマとデータベース

prisma/schema.prisma で定義したモデルと .envDATABASE_URL が一致しているか確認。

✅ 3. Auth.js の設定

src/auth.ts で以下が正しく設定されているか確認:

  • authorize 関数でパスワードハッシュを使用している
  • Prisma Adapter が正しく初期化されている
  • Credentials プロバイダーが使用されている

✅ 4. ページルートの一貫性

  • ログインフォームの action 属性が正しいルートを指している
  • signInsignOut 関数が正しくインポートされている
  • ダッシュボードページがセッション確認を実施している

✅ 5. データベースアクセス

  • Prisma クライアントが $lib/server/db.ts で正しく初期化されている
  • サーバーアクション内でのみデータベースアクセスが行われている(クライアント側からは不可)

✅ 6. パスワード処理

  • ユーザー登録時に hashPassword() でパスワードを暗号化
  • ログイン時に verifyPassword() で暗号化されたパスワードを検証

✅ 7. Tailwind CSS

src/app.css@import "tailwindcss"; が記述されているか確認


第8章:動作確認と実行

8-1. 開発サーバーの起動

Terminal window
bun run dev

8-2. 新規ユーザーの登録

ブラウザで http://localhost:5173/register にアクセスして、新しいユーザーを登録します。

登録情報の例:

  • ユーザー名: 太郎
  • メール: taro@example.com
  • パスワード: Password123

8-3. ログインの確認

http://localhost:5173/login でログインして、メールアドレスとパスワードでログインできるか確認します。

8-4. ダッシュボードの表示

ログイン成功後、ダッシュボード(/dashboard)に遷移してユーザー情報が表示されるか確認します。

8-5. ログアウトの確認

ダッシュボードのログアウトボタンをクリックして、ログイン画面に戻るか確認します。


第9章:本番環境ビルド

9-1. 本番用にビルド

Terminal window
bun run build

9-2. ビルド結果の確認

.svelte-kit/output ディレクトリが作成されます。

9-3. 本番環境での実行

Terminal window
bun run preview

よくある質問(FAQ)

Q1: パスワードを忘れた場合はどうするの?

A: 本ガイドの基本機能には含まれていません。必要な場合は、以下を実装してください:

  • パスワードリセット用のメール送信機能
  • リセットトークンの管理(Prisma モデルに追加)

Q2: OAuth(Google、GitHub ログイン)を追加したい

A: Auth.js は OAuth をサポートしています。src/auth.ts に以下を追加:

import Google from "@auth/sveltekit/providers/google";
// providers 配列に追加
Google({
clientId: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
});

Q3: ユーザープロファイルページを作りたい

A: Prisma モデルに profile フィールドを追加し、新しいルート src/routes/profile/+page.svelte を作成してください。

Q4: Bun を Node.js に変更したい

A: コマンドを以下に置き換えてください:

Terminal window
npm install # bun install の代わり
npm run dev # bun run dev の代わり

トラブルシューティング

エラー: “Cannot find module ‘$env/static/private’”

解決法: tsconfig.json に以下を追加してください:

{
"include": [".svelte-kit/ambient.d.ts"]
}

エラー: “DATABASE_URL is not set”

解決法: .env ファイルが DATABASE_URL を定義しているか確認してください。

ログインできない

解決法:

  1. メールアドレスが正確か確認
  2. パスワードが登録時と一致しているか確認
  3. ブラウザのコンソールでエラーメッセージを確認

Tailwind CSS が適用されない

解決法: 以下を確認:

  1. src/routes/+layout.sveltesrc/app.css がインポートされているか
  2. vite.config.tstailwindcss() プラグインが登録されているか
  3. 開発サーバーを再起動

まとめ

このガイドで、以下が実装されました:

Bun でのプロジェクト管理 ✅ TypeScript による型安全なコード ✅ SvelteKit でのルーティングとページ作成 ✅ Prisma によるデータベース操作 ✅ Auth.js でのユーザー認証 ✅ Tailwind CSS でのスタイリング ✅ 最低限のアカウント管理機能

次のステップとして、エラーハンドリングの改善、ユーザープロフィール機能の追加、メール認証など、さらに高度な機能を実装できます。

公式ドキュメントをぜひ参考にしてください: