Chuyển đến nội dung chính

レッスン 14: Vibe コーディング フルスタック アプリ — パート 1: バックエンド

Vibe コーディングを使用して完全なバックエンドを構築します。 Node.js + Express + TypeScript をセットアップします。 AIを活用したデータベーススキーマ設計。 REST API、認証、検証。バックエンド開発のエージェント モード ワークフロー。

💻 プログラミング — レッスン 14 レッスン 14: Vibe コーディング フルスタック アプリ — パート 1: Backend

GitHub Copilot を使用した Vibe コーディング: 基本から高度まで

パート 5: Vibe コーディングの実際 — 実際のプロジェクトの構築

xdev.asia

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 dev AIがスキーマを変更した後
  • 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 接続、レスポンシブ デザイン。