Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -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起動 |
File renamed without changes.
5 changes: 5 additions & 0 deletions apps/web/drizzle/0000_easy_electro.sql
Original file line number Diff line number Diff line change
@@ -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
);
52 changes: 52 additions & 0 deletions apps/web/drizzle/meta/0000_snapshot.json
Original file line number Diff line number Diff line change
@@ -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": {}
}
}
13 changes: 13 additions & 0 deletions apps/web/drizzle/meta/_journal.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"version": "7",
"dialect": "postgresql",
"entries": [
{
"idx": 0,
"version": "7",
"when": 1771071607317,
"tag": "0000_easy_electro",
"breakpoints": true
}
]
}
2 changes: 2 additions & 0 deletions apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@
"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"
},
"dependencies": {
"@hono/node-server": "^1.19.9",
"dotenv": "^17.3.1",
"drizzle-orm": "^0.45.1",
"hono": "^4.0.0",
"postgres": "^3.4.8",
Expand Down
18 changes: 12 additions & 6 deletions apps/web/src/db/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<typeof drizzle<typeof schema>> | 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;
}
30 changes: 30 additions & 0 deletions apps/web/src/db/seed.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import "dotenv/config";
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());
5 changes: 4 additions & 1 deletion apps/web/src/routes/api.tsx
Original file line number Diff line number Diff line change
@@ -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";

Expand All @@ -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(
<>
Expand All @@ -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) {
Expand All @@ -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);
Expand Down
8 changes: 7 additions & 1 deletion biome.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
]
}
}
13 changes: 13 additions & 0 deletions docs/log/0001-initial-setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 を追加 (技術スタック、セットアップ手順、コマンド一覧)
9 changes: 9 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.