演習:Copilotでアプリを構築しよう
ストーリー
「理論は十分だ。Copilotをフル活用して、ミニアプリを一つ作ってみよう」
中島先輩が課題を出した。
「TODOアプリのバックエンドAPIだ。Copilotの全機能を使って構築してくれ」
「全部Copilotで書くんですか?」
「いいや。設計は自分で考える。実装の手間をCopilotで省く。 この感覚を体で覚えてほしい」
課題: TODOアプリのバックエンドAPI構築
全体要件
技術スタック: TypeScript + Express.js
機能:
- TODO の CRUD(作成、読取、更新、削除)
- ステータス管理(pending, in_progress, done)
- 優先度(high, medium, low)
評価ポイント:
- Copilot の補完をどれだけ活用できたか
- 型安全な実装になっているか
- テストが含まれているか
Phase 1: 型定義を作成(15分)
やること
types.tsファイルを作成- コメントで型の意図を記述
- Copilot に型定義を補完させる
最低限必要な型
- Todo: id, title, description, status, priority, createdAt, updatedAt
- CreateTodoInput: 作成時の入力
- UpdateTodoInput: 更新時の入力
- TodoStatus: 'pending' | 'in_progress' | 'done'
- Priority: 'high' | 'medium' | 'low'
<details>
<summary>解答例(自分で実装してから確認しよう)</summary>
typescript
// types.ts
/** TODOのステータス */
type TodoStatus = 'pending' | 'in_progress' | 'done';
/** 優先度 */
type Priority = 'high' | 'medium' | 'low';
/** TODOエンティティ */
interface Todo {
id: string;
title: string;
description: string;
status: TodoStatus;
priority: Priority;
createdAt: Date;
updatedAt: Date;
}
/** TODO作成の入力 */
interface CreateTodoInput {
title: string;
description?: string;
priority?: Priority;
}
/** TODO更新の入力 */
interface UpdateTodoInput {
title?: string;
description?: string;
status?: TodoStatus;
priority?: Priority;
}
/** APIレスポンスの共通型 */
interface ApiResponse<T> {
success: boolean;
data?: T;
error?: string;
}Phase 2: ルーティングを実装(20分)
やること
routes/todos.tsを作成- 各エンドポイントのコメントを先に書く
- Copilotにルーティング実装を補完させる
必要なエンドポイント
GET /api/todos - 一覧取得(フィルタ: status, priority)
GET /api/todos/:id - 詳細取得
POST /api/todos - 作成
PUT /api/todos/:id - 更新
DELETE /api/todos/:id - 削除
PATCH /api/todos/:id/status - ステータス変更
<details>
<summary>解答例(自分で実装してから確認しよう)</summary>
typescript
// routes/todos.ts
import { Router, Request, Response } from 'express';
import { Todo, CreateTodoInput, UpdateTodoInput, ApiResponse } from '../types';
import { randomUUID } from 'crypto';
const router = Router();
const todos: Todo[] = [];
// TODO一覧取得(フィルタ対応)
router.get('/', (req: Request, res: Response<ApiResponse<Todo[]>>) => {
const { status, priority } = req.query;
let filtered = [...todos];
if (status) {
filtered = filtered.filter(t => t.status === status);
}
if (priority) {
filtered = filtered.filter(t => t.priority === priority);
}
res.json({ success: true, data: filtered });
});
// TODO詳細取得
router.get('/:id', (req: Request, res: Response<ApiResponse<Todo>>) => {
const todo = todos.find(t => t.id === req.params.id);
if (!todo) {
return res.status(404).json({ success: false, error: 'TODOが見つかりません' });
}
res.json({ success: true, data: todo });
});
// TODO作成
router.post('/', (req: Request<{}, {}, CreateTodoInput>, res: Response<ApiResponse<Todo>>) => {
const { title, description, priority } = req.body;
if (!title || title.trim().length === 0) {
return res.status(400).json({ success: false, error: 'タイトルは必須です' });
}
const now = new Date();
const todo: Todo = {
id: randomUUID(),
title: title.trim(),
description: description || '',
status: 'pending',
priority: priority || 'medium',
createdAt: now,
updatedAt: now,
};
todos.push(todo);
res.status(201).json({ success: true, data: todo });
});
// TODO更新
router.put('/:id', (req: Request<{ id: string }, {}, UpdateTodoInput>, res: Response<ApiResponse<Todo>>) => {
const index = todos.findIndex(t => t.id === req.params.id);
if (index === -1) {
return res.status(404).json({ success: false, error: 'TODOが見つかりません' });
}
todos[index] = {
...todos[index],
...req.body,
updatedAt: new Date(),
};
res.json({ success: true, data: todos[index] });
});
// TODO削除
router.delete('/:id', (req: Request, res: Response<ApiResponse<null>>) => {
const index = todos.findIndex(t => t.id === req.params.id);
if (index === -1) {
return res.status(404).json({ success: false, error: 'TODOが見つかりません' });
}
todos.splice(index, 1);
res.json({ success: true, data: null });
});
export default router;Phase 3: テストを生成(25分)
やること
__tests__/todos.test.tsを作成- Copilotの
/testsコマンドを活用 - エッジケースのテストを自分で追加
テストすべき観点
- 一覧取得(フィルタあり/なし)
- 存在するIDでの詳細取得
- 存在しないIDでの404エラー
- 正常な作成
- タイトル未入力での作成(バリデーションエラー)
- ステータス更新
- 削除
<details>
<summary>解答例(自分で実装してから確認しよう)</summary>
typescript
// __tests__/todos.test.ts
import request from 'supertest';
import app from '../app';
describe('TODO API', () => {
let createdTodoId: string;
describe('POST /api/todos', () => {
it('should create a new todo', async () => {
const res = await request(app)
.post('/api/todos')
.send({ title: 'テストTODO', priority: 'high' });
expect(res.status).toBe(201);
expect(res.body.success).toBe(true);
expect(res.body.data.title).toBe('テストTODO');
expect(res.body.data.status).toBe('pending');
createdTodoId = res.body.data.id;
});
it('should return 400 for empty title', async () => {
const res = await request(app)
.post('/api/todos')
.send({ title: '' });
expect(res.status).toBe(400);
expect(res.body.success).toBe(false);
});
});
describe('GET /api/todos', () => {
it('should return all todos', async () => {
const res = await request(app).get('/api/todos');
expect(res.status).toBe(200);
expect(res.body.data).toBeInstanceOf(Array);
});
});
describe('GET /api/todos/:id', () => {
it('should return 404 for non-existent id', async () => {
const res = await request(app).get('/api/todos/non-existent-id');
expect(res.status).toBe(404);
});
});
describe('DELETE /api/todos/:id', () => {
it('should delete a todo', async () => {
const res = await request(app).delete(`/api/todos/${createdTodoId}`);
expect(res.status).toBe(200);
expect(res.body.success).toBe(true);
});
});
});Phase 4: Copilot Chatでリファクタリング(30分)
やること
- Phase 2で作成したコードをCopilot Chatでレビュー
- 指摘に基づいてリファクタリング
- バリデーションの追加(zodの活用など)
Copilot Chat への質問例:
@workspace 現在のtodos.tsのコードをレビューして、
以下の観点で改善してください:
1. バリデーションをzodで実装
2. エラーハンドリングの統一
3. ビジネスロジックをルーターから分離
</details>
達成度チェック
| Phase | テーマ | 完了 |
|---|---|---|
| Phase 1 | 型定義の作成 | [ ] |
| Phase 2 | ルーティング実装 | [ ] |
| Phase 3 | テスト生成 | [ ] |
| Phase 4 | リファクタリング | [ ] |
まとめ
| ポイント | 内容 |
|---|---|
| 設計は人間 | 型定義やエンドポイント設計は自分で考える |
| 実装はCopilot | コメント駆動とコンテキスト設定で効率化 |
| テストは協働 | 骨格はAI、エッジケースは人間が追加 |
| レビューはChat | Copilot Chatで改善ポイントを洗い出す |
チェックリスト
- 型定義をコメント駆動で生成できた
- CRUDエンドポイントをCopilotの補完で実装できた
- /tests でテストコードを生成し、エッジケースを追加した
- Copilot Chatでレビュー・リファクタリングを実行した
次のステップへ
お疲れさまでした。Copilotでのアプリ構築演習が完了しました。 次はStep 4のチェックポイントです。
推定読了時間: 90分