1. 実際のプロジェクトの紹介
In the next 3 lessons, we will build Task Management App Vibeコーディングを完了します。目標は、AI を使用したエンドツーエンドの開発プロセスを体験することです。
プロジェクトの概要:
| レイヤー | テクノロジー | レッスン |
|---|---|---|
| バックエンド | Node.js + Express + TypeScript | レッスン 14 (このレッスン) |
| フロントエンド | React + Next.js + TailwindCSS | レッスン 15 |
| モバイル | React Native / Flutter | レッスン 16 |
| データベース | PostgreSQL + Prisma ORM | Lesson 14 |
| 認証 | JWT + bcrypt | Lesson 14 |
2. Vibecodingを使用してプロジェクトを作成する
ステップ 1: エージェント モードを使用してスキャフォールディングする
// Prompt cho Agent Mode:
Create a new Node.js backend project with:
- TypeScript configuration
- Express.js server
- Prisma ORM with PostgreSQL
- JWT authentication
- Project structure following clean architecture
- ESLint + Prettier configuration
- Docker Compose for PostgreSQL
エージェント モードでは、次のことが自動的に行われます。
- 走る
npm init、依存関係をインストールします - 作成
tsconfig.json、.eslintrc、.prettierrc - Create folder structure
- Setup Docker Compose
ステップ 2: プロジェクト構造の作成
task-api/
├── src/
│ ├── controllers/
│ │ ├── auth.controller.ts
│ │ └── task.controller.ts
│ ├── middleware/
│ │ ├── auth.middleware.ts
│ │ └── validate.middleware.ts
│ ├── routes/
│ │ ├── auth.routes.ts
│ │ └── task.routes.ts
│ ├── services/
│ │ ├── auth.service.ts
│ │ └── task.service.ts
│ ├── types/
│ │ └── index.ts
│ └── app.ts
├── prisma/
│ └── schema.prisma
├── docker-compose.yml
├── tsconfig.json
└── package.json
3. AIを活用したデータベーススキーマ設計
// Prompt:
Design a Prisma schema for a task management app with:
- Users (email, password hash, name, avatar)
- Projects (name, description, owner)
- Tasks (title, description, status, priority, assignee, due date)
- Comments on tasks
- Project memberships with roles
Copilot はスキーマを生成します。
// prisma/schema.prisma
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model User {
id String @id @default(uuid())
email String @unique
password String
name String
avatar String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
ownedProjects Project[]
memberships ProjectMember[]
assignedTasks Task[] @relation("AssignedTasks")
createdTasks Task[] @relation("CreatedTasks")
comments Comment[]
}
model Project {
id String @id @default(uuid())
name String
description String?
ownerId String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
owner User @relation(fields: [ownerId], references: [id])
members ProjectMember[]
tasks Task[]
}
model ProjectMember {
id String @id @default(uuid())
role Role @default(MEMBER)
userId String
projectId String
user User @relation(fields: [userId], references: [id])
project Project @relation(fields: [projectId], references: [id])
@@unique([userId, projectId])
}
enum Role {
OWNER
ADMIN
MEMBER
}
model Task {
id String @id @default(uuid())
title String
description String?
status TaskStatus @default(TODO)
priority Priority @default(MEDIUM)
dueDate DateTime?
projectId String
assigneeId String?
creatorId String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
project Project @relation(fields: [projectId], references: [id])
assignee User? @relation("AssignedTasks", fields: [assigneeId], references: [id])
creator User @relation("CreatedTasks", fields: [creatorId], references: [id])
comments Comment[]
}
enum TaskStatus {
TODO
IN_PROGRESS
IN_REVIEW
DONE
}
enum Priority {
LOW
MEDIUM
HIGH
URGENT
}
model Comment {
id String @id @default(uuid())
content String
taskId String
authorId String
createdAt DateTime @default(now())
task Task @relation(fields: [taskId], references: [id])
author User @relation(fields: [authorId], references: [id])
}
4. 認証 — JWT フロー
// Prompt:
Implement JWT authentication with:
- Register endpoint with email validation and password hashing
- Login endpoint returning access + refresh tokens
- Auth middleware that validates JWT
- Refresh token rotation
- Use bcrypt for password hashing
Copilot は認証サービスを生成します。
// src/services/auth.service.ts
import bcrypt from 'bcrypt';
import jwt from 'jsonwebtoken';
import { prisma } from '../lib/prisma';
export class AuthService {
async register(email: string, password: string, name: string) {
const existingUser = await prisma.user.findUnique({ where: { email } });
if (existingUser) {
throw new Error('Email already registered');
}
const hashedPassword = await bcrypt.hash(password, 12);
const user = await prisma.user.create({
data: { email, password: hashedPassword, name },
});
return this.generateTokens(user.id);
}
async login(email: string, password: string) {
const user = await prisma.user.findUnique({ where: { email } });
if (!user || !(await bcrypt.compare(password, user.password))) {
throw new Error('Invalid credentials');
}
return this.generateTokens(user.id);
}
private generateTokens(userId: string) {
const accessToken = jwt.sign(
{ userId },
process.env.JWT_SECRET!,
{ expiresIn: '15m' }
);
const refreshToken = jwt.sign(
{ userId },
process.env.JWT_REFRESH_SECRET!,
{ expiresIn: '7d' }
);
return { accessToken, refreshToken };
}
}
5. REST API — CRUD 操作
// Prompt:
Create CRUD endpoints for tasks with:
- GET /api/tasks — list tasks with filtering and pagination
- POST /api/tasks — create new task
- PUT /api/tasks/:id — update task
- DELETE /api/tasks/:id — delete task
- Include authorization checks
- Validate request body with Zod
Copilot generate controller:
// src/controllers/task.controller.ts
import { Request, Response } from 'express';
import { z } from 'zod';
import { prisma } from '../lib/prisma';
const createTaskSchema = z.object({
title: z.string().min(1).max(255),
description: z.string().optional(),
priority: z.enum(['LOW', 'MEDIUM', 'HIGH', 'URGENT']).default('MEDIUM'),
dueDate: z.string().datetime().optional(),
projectId: z.string().uuid(),
assigneeId: z.string().uuid().optional(),
});
export class TaskController {
async list(req: Request, res: Response) {
const { projectId, status, priority, page = '1', limit = '20' } = req.query;
const tasks = await prisma.task.findMany({
where: {
projectId: projectId as string,
status: status as any,
priority: priority as any,
},
include: {
assignee: { select: { id: true, name: true, avatar: true } },
_count: { select: { comments: true } },
},
skip: (Number(page) - 1) * Number(limit),
take: Number(limit),
orderBy: { createdAt: 'desc' },
});
res.json({ data: tasks });
}
async create(req: Request, res: Response) {
const data = createTaskSchema.parse(req.body);
const task = await prisma.task.create({
data: {
...data,
dueDate: data.dueDate ? new Date(data.dueDate) : null,
creatorId: req.userId!, // from auth middleware
},
});
res.status(201).json({ data: task });
}
}
6. Vibe Coding Workflow for Backend
Optimal process:
┌──────────────────────────────────────────────────┐
│ BACKEND VIBE CODING WORKFLOW │
├──────────────────────────────────────────────────┤
│ │
│ 1. Mô tả feature bằng ngôn ngữ tự nhiên │
│ ↓ │
│ 2. Agent Mode scaffold files │
│ ↓ │
│ 3. Review generated code │
│ ↓ │
│ 4. Prompt: "Write tests for this" │
│ ↓ │
│ 5. Agent chạy tests, fix lỗi │
│ ↓ │
│ 6. Prompt: "Add error handling and validation" │
│ ↓ │
│ 7. Review final code → Commit │
│ │
└──────────────────────────────────────────────────┘
Vibe コーディング バックエンドのヒント:
- ビジネスロジックを明確に記述する: AI はあなたのドメインを知りません
- 同時にテストをリクエストする: 「サービスとその単体テストを作成します」
- Review security: 入力検証、認証チェックを常にチェックします
- データベースの移行: 使用する
prisma migrate devAIがスキーマを変更した後 - copilot-instructions.md を使用する: プロジェクトのコーディング標準を定義する
7. AIによるテスト
// Prompt:
Write comprehensive tests for AuthService including:
- Successful registration
- Duplicate email registration
- Successful login
- Invalid credentials
- Token generation and verification
Use Jest with supertest for integration tests.
// src/services/__tests__/auth.service.test.ts
import { AuthService } from '../auth.service';
import { prisma } from '../../lib/prisma';
import bcrypt from 'bcrypt';
describe('AuthService', () => {
const authService = new AuthService();
beforeEach(async () => {
await prisma.user.deleteMany();
});
describe('register', () => {
it('should create user and return tokens', async () => {
const result = await authService.register(
'[email protected]', 'Password123!', 'Test User'
);
expect(result.accessToken).toBeDefined();
expect(result.refreshToken).toBeDefined();
const user = await prisma.user.findUnique({
where: { email: '[email protected]' },
});
expect(user).toBeDefined();
expect(user!.name).toBe('Test User');
});
it('should hash password correctly', async () => {
await authService.register(
'[email protected]', 'Password123!', 'Test User'
);
const user = await prisma.user.findUnique({
where: { email: '[email protected]' },
});
expect(user!.password).not.toBe('Password123!');
expect(await bcrypt.compare('Password123!', user!.password)).toBe(true);
});
it('should reject duplicate email', async () => {
await authService.register(
'[email protected]', 'Password123!', 'Test User'
);
await expect(
authService.register('[email protected]', 'Other456!', 'Other User')
).rejects.toThrow('Email already registered');
});
});
});
8. Error Handling Pattern
// Prompt:
Add global error handling middleware with:
- Custom AppError class with status codes
- Async error wrapper for controllers
- Prisma error handling (unique constraint, not found)
- Request validation errors (Zod)
- Structured error response format
9. まとめ
| ステップ | Vibe Coding Approach | 時間の節約 |
|---|---|---|
| プロジェクトのセットアップ | エージェントモードの足場 | ~2時間→5分 |
| DBスキーマ | エンティティの記述 → Prisma スキーマ | ~1 hour → 10 minutes |
| 認証 | フローの説明→完全実装 | 約4時間→20分 |
| CRUD API | エンドポイント → コントローラー + サービスの説明 | ~3時間→15分 |
| テスト | 「X のテストの作成」 → 包括的なスイート | ~3時間→10分 |
重要な注意事項: 上記の時間の節約は理想的です。実はまだ必要なんです レビュー、デバッグ、改良 AIコードが誕生します。 Vibe コーディングはコーディング時間を短縮しますが、 レビュー時間を短縮しない。
次の記事: 建設 フロントエンド アプリ用 — React + Next.js + TailwindCSS、API 接続、レスポンシブ デザイン。