From 0326bf4e546657c4dd71c8f0252ad385513273ae Mon Sep 17 00:00:00 2001 From: mie035 Date: Sat, 14 Feb 2026 21:03:01 +0900 Subject: [PATCH 1/7] fix: lazy init db connection to avoid startup error without DATABASE_URL Co-Authored-By: Claude Opus 4.6 --- apps/web/src/db/index.ts | 18 ++++++++++++------ apps/web/src/routes/api.tsx | 5 ++++- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/apps/web/src/db/index.ts b/apps/web/src/db/index.ts index eccb754..d277e0f 100644 --- a/apps/web/src/db/index.ts +++ b/apps/web/src/db/index.ts @@ -2,10 +2,16 @@ import { drizzle } from "drizzle-orm/postgres-js"; import postgres from "postgres"; import * as schema from "./schema"; -const databaseUrl = process.env.DATABASE_URL; -if (!databaseUrl) { - throw new Error("DATABASE_URL is not set"); -} +let _db: ReturnType> | null = null; -const client = postgres(databaseUrl); -export const db = drizzle(client, { schema }); +export function getDb() { + if (!_db) { + const databaseUrl = process.env.DATABASE_URL; + if (!databaseUrl) { + throw new Error("DATABASE_URL is not set"); + } + const client = postgres(databaseUrl); + _db = drizzle(client, { schema }); + } + return _db; +} diff --git a/apps/web/src/routes/api.tsx b/apps/web/src/routes/api.tsx index 33ecd3a..972b9f8 100644 --- a/apps/web/src/routes/api.tsx +++ b/apps/web/src/routes/api.tsx @@ -1,7 +1,7 @@ import { eq } from "drizzle-orm"; import { Hono } from "hono"; import { z } from "zod/v4"; -import { db } from "../db"; +import { getDb } from "../db"; import { examples } from "../db/schema"; import { ExampleItem } from "../views/partials/example-item"; @@ -12,6 +12,7 @@ const createExampleSchema = z.object({ }); app.get("/examples", async (c) => { + const db = getDb(); const items = await db.select().from(examples); return c.html( <> @@ -23,6 +24,7 @@ app.get("/examples", async (c) => { }); app.post("/examples", async (c) => { + const db = getDb(); const body = await c.req.parseBody(); const parsed = createExampleSchema.safeParse(body); if (!parsed.success) { @@ -40,6 +42,7 @@ app.post("/examples", async (c) => { }); app.delete("/examples/:id", async (c) => { + const db = getDb(); const id = c.req.param("id"); await db.delete(examples).where(eq(examples.id, id)); return c.body(null, 200); From 18fee4e01c126124220d981cbd5ec18b1f320a06 Mon Sep 17 00:00:00 2001 From: mie035 Date: Sat, 14 Feb 2026 21:07:42 +0900 Subject: [PATCH 2/7] feat: add db seed script Co-Authored-By: Claude Opus 4.6 --- apps/web/package.json | 1 + apps/web/src/db/seed.ts | 27 +++++++++++++++++++++++++++ 2 files changed, 28 insertions(+) create mode 100644 apps/web/src/db/seed.ts diff --git a/apps/web/package.json b/apps/web/package.json index da1204b..7c6fe18 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -8,6 +8,7 @@ "db:generate": "drizzle-kit generate", "db:migrate": "drizzle-kit migrate", "db:studio": "drizzle-kit studio", + "db:seed": "tsx src/db/seed.ts", "test": "vitest run", "test:watch": "vitest", "test:e2e": "playwright test" diff --git a/apps/web/src/db/seed.ts b/apps/web/src/db/seed.ts new file mode 100644 index 0000000..b4087f0 --- /dev/null +++ b/apps/web/src/db/seed.ts @@ -0,0 +1,27 @@ +import { drizzle } from "drizzle-orm/postgres-js"; +import postgres from "postgres"; +import { examples } from "./schema"; + +const databaseUrl = process.env.DATABASE_URL; +if (!databaseUrl) { + throw new Error("DATABASE_URL is not set"); +} + +const client = postgres(databaseUrl); +const db = drizzle(client); + +async function seed() { + console.log("Seeding..."); + + await db.insert(examples).values([ + { title: "Example 1" }, + { title: "Example 2" }, + { title: "Example 3" }, + ]); + + console.log("Seeding done."); +} + +seed() + .catch(console.error) + .finally(() => client.end()); From f5f4a4450706e5b025ebee0e4b6e5ca5552753b8 Mon Sep 17 00:00:00 2001 From: mie035 Date: Sat, 14 Feb 2026 21:21:04 +0900 Subject: [PATCH 3/7] feat: add dotenv to seed script and initial migration Co-Authored-By: Claude Opus 4.6 --- apps/web/.env.example | 1 + apps/web/drizzle/0000_easy_electro.sql | 5 +++ apps/web/drizzle/meta/0000_snapshot.json | 52 ++++++++++++++++++++++++ apps/web/drizzle/meta/_journal.json | 13 ++++++ apps/web/package.json | 1 + apps/web/src/db/seed.ts | 1 + pnpm-lock.yaml | 9 ++++ 7 files changed, 82 insertions(+) create mode 100644 apps/web/.env.example create mode 100644 apps/web/drizzle/0000_easy_electro.sql create mode 100644 apps/web/drizzle/meta/0000_snapshot.json create mode 100644 apps/web/drizzle/meta/_journal.json diff --git a/apps/web/.env.example b/apps/web/.env.example new file mode 100644 index 0000000..67b1977 --- /dev/null +++ b/apps/web/.env.example @@ -0,0 +1 @@ +DATABASE_URL=postgres://postgres:postgres@localhost:5432/nagauta_stack diff --git a/apps/web/drizzle/0000_easy_electro.sql b/apps/web/drizzle/0000_easy_electro.sql new file mode 100644 index 0000000..6396975 --- /dev/null +++ b/apps/web/drizzle/0000_easy_electro.sql @@ -0,0 +1,5 @@ +CREATE TABLE "examples" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "title" text NOT NULL, + "created_at" timestamp DEFAULT now() NOT NULL +); diff --git a/apps/web/drizzle/meta/0000_snapshot.json b/apps/web/drizzle/meta/0000_snapshot.json new file mode 100644 index 0000000..77cab7f --- /dev/null +++ b/apps/web/drizzle/meta/0000_snapshot.json @@ -0,0 +1,52 @@ +{ + "id": "c3bec21b-9eef-4704-8f29-055429c8946c", + "prevId": "00000000-0000-0000-0000-000000000000", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.examples": { + "name": "examples", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/apps/web/drizzle/meta/_journal.json b/apps/web/drizzle/meta/_journal.json new file mode 100644 index 0000000..937dac4 --- /dev/null +++ b/apps/web/drizzle/meta/_journal.json @@ -0,0 +1,13 @@ +{ + "version": "7", + "dialect": "postgresql", + "entries": [ + { + "idx": 0, + "version": "7", + "when": 1771071607317, + "tag": "0000_easy_electro", + "breakpoints": true + } + ] +} \ No newline at end of file diff --git a/apps/web/package.json b/apps/web/package.json index 7c6fe18..7b9abb0 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -15,6 +15,7 @@ }, "dependencies": { "@hono/node-server": "^1.19.9", + "dotenv": "^17.3.1", "drizzle-orm": "^0.45.1", "hono": "^4.0.0", "postgres": "^3.4.8", diff --git a/apps/web/src/db/seed.ts b/apps/web/src/db/seed.ts index b4087f0..3a967b5 100644 --- a/apps/web/src/db/seed.ts +++ b/apps/web/src/db/seed.ts @@ -1,3 +1,4 @@ +import "dotenv/config"; import { drizzle } from "drizzle-orm/postgres-js"; import postgres from "postgres"; import { examples } from "./schema"; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a5f2c51..33549ae 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -26,6 +26,9 @@ importers: '@hono/node-server': specifier: ^1.19.9 version: 1.19.9(hono@4.11.9) + dotenv: + specifier: ^17.3.1 + version: 17.3.1 drizzle-orm: specifier: ^0.45.1 version: 0.45.1(postgres@3.4.8) @@ -1001,6 +1004,10 @@ packages: resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} engines: {node: '>=8'} + dotenv@17.3.1: + resolution: {integrity: sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA==} + engines: {node: '>=12'} + drizzle-kit@0.31.9: resolution: {integrity: sha512-GViD3IgsXn7trFyBUUHyTFBpH/FsHTxYJ66qdbVggxef4UBPHRYxQaRzYLTuekYnk9i5FIEL9pbBIwMqX/Uwrg==} hasBin: true @@ -2195,6 +2202,8 @@ snapshots: detect-libc@2.1.2: {} + dotenv@17.3.1: {} + drizzle-kit@0.31.9: dependencies: '@drizzle-team/brocli': 0.10.2 From c27178e24462ad13372893f197c872aa46e5b198 Mon Sep 17 00:00:00 2001 From: mie035 Date: Sat, 14 Feb 2026 21:21:50 +0900 Subject: [PATCH 4/7] chore: remove unused root .env.example Co-Authored-By: Claude Opus 4.6 --- .env.example | 1 - 1 file changed, 1 deletion(-) delete mode 100644 .env.example diff --git a/.env.example b/.env.example deleted file mode 100644 index 67b1977..0000000 --- a/.env.example +++ /dev/null @@ -1 +0,0 @@ -DATABASE_URL=postgres://postgres:postgres@localhost:5432/nagauta_stack From 7024b3b61c14f6eb50bc35755f2f7b836cb47dc0 Mon Sep 17 00:00:00 2001 From: mie035 Date: Sat, 14 Feb 2026 21:23:10 +0900 Subject: [PATCH 5/7] docs: add README Co-Authored-By: Claude Opus 4.6 --- README.md | 60 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..5d38411 --- /dev/null +++ b/README.md @@ -0,0 +1,60 @@ +# nagauta-stack + +個人開発のベースとなるテンプレートレポジトリ。 + +## 技術スタック + +| カテゴリ | 技術 | +|---------|------| +| FW | Hono | +| フロント | htmx | +| CSS | Tailwind CSS | +| DB | PostgreSQL + Drizzle ORM | +| バリデーション | Zod | +| コンテナ | Docker | +| モノレポ | pnpm + Turborepo | +| Lint / Format | Biome | +| Git Hooks | Lefthook | +| テスト | Vitest + Playwright | +| CI/CD | GitHub Actions | +| 依存更新 | Renovate | + +## 構成 + +``` +apps/ + web/ # Hono アプリ +packages/ + config/ # 共有設定 (tsconfig) +``` + +## セットアップ + +```bash +pnpm install +docker compose up -d +cp apps/web/.env.example apps/web/.env +pnpm --filter @nagauta-stack/web db:migrate +pnpm --filter @nagauta-stack/web db:seed +``` + +## 開発 + +```bash +pnpm dev +``` + +## コマンド + +| コマンド | 説明 | +|---------|------| +| `pnpm dev` | 開発サーバー起動 | +| `pnpm build` | ビルド | +| `pnpm lint` | Lint / Format チェック | +| `pnpm lint:fix` | Lint / Format 自動修正 | +| `pnpm check-types` | 型チェック | +| `pnpm test` | テスト実行 | +| `pnpm --filter @nagauta-stack/web db:generate` | マイグレーションファイル生成 | +| `pnpm --filter @nagauta-stack/web db:migrate` | マイグレーション実行 | +| `pnpm --filter @nagauta-stack/web db:seed` | Seedデータ投入 | +| `pnpm --filter @nagauta-stack/web db:studio` | Drizzle Studio起動 | From 530422b3f76372a8ec87129357b275652ab42059 Mon Sep 17 00:00:00 2001 From: mie035 Date: Sat, 14 Feb 2026 21:25:46 +0900 Subject: [PATCH 6/7] fix: ignore drizzle dir in biome and fix formatting Co-Authored-By: Claude Opus 4.6 --- apps/web/src/db/seed.ts | 12 +++++++----- biome.json | 8 +++++++- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/apps/web/src/db/seed.ts b/apps/web/src/db/seed.ts index 3a967b5..4261d9d 100644 --- a/apps/web/src/db/seed.ts +++ b/apps/web/src/db/seed.ts @@ -14,11 +14,13 @@ const db = drizzle(client); async function seed() { console.log("Seeding..."); - await db.insert(examples).values([ - { title: "Example 1" }, - { title: "Example 2" }, - { title: "Example 3" }, - ]); + await db + .insert(examples) + .values([ + { title: "Example 1" }, + { title: "Example 2" }, + { title: "Example 3" }, + ]); console.log("Seeding done."); } diff --git a/biome.json b/biome.json index becf0c3..3b4acbd 100644 --- a/biome.json +++ b/biome.json @@ -20,6 +20,12 @@ "indentStyle": "tab" }, "files": { - "ignore": ["node_modules", "dist", "public/styles.css", ".turbo"] + "ignore": [ + "node_modules", + "dist", + "public/styles.css", + ".turbo", + "apps/web/drizzle" + ] } } From ee493e77285c0e5c7d5f330069ed977b8867dcac Mon Sep 17 00:00:00 2001 From: mie035 Date: Sat, 14 Feb 2026 21:25:52 +0900 Subject: [PATCH 7/7] docs: update log with db setup details Co-Authored-By: Claude Opus 4.6 --- docs/log/0001-initial-setup.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/docs/log/0001-initial-setup.md b/docs/log/0001-initial-setup.md index 495d6ff..f88dc4e 100644 --- a/docs/log/0001-initial-setup.md +++ b/docs/log/0001-initial-setup.md @@ -115,3 +115,16 @@ - [x] Renovate 設定 - [x] CLAUDE.md - [x] .claude/skills/commit + .claude/agents/ + +### 9. DB周りの整備 (PR #2) + +- DB接続をトップレベルの即時初期化から `getDb()` による遅延初期化に変更 + - DATABASE_URL 未設定時にサーバー起動がクラッシュする問題を修正 +- Seed スクリプト (`db:seed`) を追加 + - dotenv を使って `apps/web/.env` を自動読み込み + - `pnpm --filter @nagauta-stack/web db:seed` で実行可能 +- `.env` は Turborepo 公式推奨に従い各app (`apps/web/`) に配置 + - ルートの `.env.example` は削除 +- 初回マイグレーションファイルを生成・コミット +- Biome の ignore に `apps/web/drizzle` (自動生成ファイル) を追加 +- README を追加 (技術スタック、セットアップ手順、コマンド一覧)