nodejs について
nodejsは、テストしやすいサービスを構築するTypeScript製Node.jsバックエンド向けのパターン集です。レイヤードアーキテクチャ、非同期エラー処理、設定管理、依存性注入を扱います。
主な機能
- 01ルート、コントローラー、サービス、リポジトリの分割
- 02async/awaitによるエラー処理
- 03TypeScriptのリクエスト・レスポンス型
- 04環境変数と統合設定の管理
- 05依存性注入とカスタムエラー
活用シーン
- 01Node.jsバックエンドサービスの層構造の整理
- 02非同期リクエストと並列処理のエラー対応
- 03環境設定の一元的な検証
- 04テストしやすいバックエンドコードの構成
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ blencorp/ claude-code-kit --skill nodejsインストール後、Claude Code で/nodejsを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
skill-rules-fragment.jsonresources/architecture-patterns.mdasync-and-errors.mdtesting-guide.md詳細
Node.js バックエンドパターン
目的
クリーンアーキテクチャ、エラー処理、テスト容易性を重視した、スケーラブルな Node.js バックエンドアプリケーションを構築するための主要なパターンです。
このスキルを使用する場面
- Node.js バックエンドサービスの構築
- async/await パターンの実装
- エラー処理とロギング
- 設定管理
- バックエンドコードのテスト
- レイヤードアーキテクチャ(ルート → コントローラー → サービス → リポジトリ)
クイックスタート
レイヤードアーキテクチャ
src/
├── api/
│ ├── routes/ # HTTP route definitions
│ ├── controllers/ # Request/response handling
│ ├── services/ # Business logic
│ └── repositories/ # Data access
├── middleware/ # Express middleware
├── types/ # TypeScript types
├── config/ # Configuration
└── utils/ # Utilities
フロー: ルート → コントローラー → サービス → リポジトリ → データベース
Async/Await のエラー処理
基本パターン
async function fetchUser(id: string): Promise<User> {
try {
const user = await db.user.findUnique({ where: { id } });
if (!user) {
throw new Error('User not found');
}
return user;
} catch (error) {
console.error('Error fetching user:', error);
throw error;
}
}
非同期コントローラーパターン
class UserController {
async getUser(req: Request, res: Response): Promise<void> {
try {
const { id } = req.params;
const user = await this.userService.getById(id);
res.json({
success: true,
data: user,
});
} catch (error) {
console.error('Error in getUser:', error);
res.status(500).json({
success: false,
error: 'Failed to fetch user',
});
}
}
}
並列処理のための Promise.all
async function getUserDashboard(userId: string) {
try {
const [user, posts, followers] = await Promise.all([
userService.getById(userId),
postService.getByUser(userId),
followerService.getByUser(userId),
]);
return { user, posts, followers };
} catch (error) {
console.error('Error loading dashboard:', error);
throw error;
}
}
TypeScript パターン
リクエスト/レスポンスの型
// Request body
interface CreateUserRequest {
email: string;
name: string;
password: string;
}
// Response
interface ApiResponse<T> {
success: boolean;
data?: T;
error?: string;
message?: string;
}
// Usage
async function createUser(
req: Request<{}, {}, CreateUserRequest>,
res: Response<ApiResponse<User>>
): Promise<void> {
const { email, name, password } = req.body;
const user = await userService.create({ email, name, password });
res.json({
success: true,
data: user,
});
}
サービスレイヤーの型
interface IUserService {
getById(id: string): Promise<User>;
create(data: CreateUserDto): Promise<User>;
update(id: string, data: UpdateUserDto): Promise<User>;
delete(id: string): Promise<void>;
}
class UserService implements IUserService {
async getById(id: string): Promise<User> {
// Implementation
}
async create(data: CreateUserDto): Promise<User> {
// Implementation
}
async update(id: string, data: UpdateUserDto): Promise<User> {
// Implementation
}
async delete(id: string): Promise<void> {
// Implementation
}
}
設定管理
環境変数
// config/env.ts
import { z } from 'zod';
const envSchema = z.object({
NODE_ENV: z.enum(['development', 'production', 'test']),
PORT: z.string().transform(Number),
DATABASE_URL: z.string().url(),
JWT_SECRET: z.string().min(32),
LOG_LEVEL: z.enum(['error', 'warn', 'info', 'debug']).default('info'),
});
export const env = envSchema.parse(process.env);
統一設定
// config/index.ts
interface Config {
server: {
port: number;
host: string;
};
database: {
url: string;
};
auth: {
jwtSecret: string;
jwtExpiry: string;
};
}
export const config: Config = {
server: {
port: parseInt(process.env.PORT || '3000'),
host: process.env.HOST || 'localhost',
},
database: {
url: process.env.DATABASE_URL || '',
},
auth: {
jwtSecret: process.env.JWT_SECRET || '',
jwtExpiry: process.env.JWT_EXPIRY || '7d',
},
};
レイヤードアーキテクチャ
コントローラーレイヤー
// controllers/UserController.ts
export class UserController {
constructor(private userService: UserService) {}
async getById(req: Request, res: Response): Promise<void> {
const { id } = req.params;
const user = await this.userService.getById(id);
res.json({
success: true,
data: user,
});
}
async create(req: Request, res: Response): Promise<void> {
const userData = req.body;
const user = await this.userService.create(userData);
res.status(201).json({
success: true,
data: user,
});
}
}
サービスレイヤー
// services/UserService.ts
export class UserService {
constructor(private userRepository: UserRepository) {}
async getById(id: string): Promise<User> {
const user = await this.userRepository.findById(id);
if (!user) {
throw new Error('User not found');
}
return user;
}
async create(data: CreateUserDto): Promise<User> {
// Business logic
const hashedPassword = await this.hashPassword(data.password);
return this.userRepository.create({
...data,
password: hashedPassword,
});
}
private async hashPassword(password: string): Promise<string> {
// Hash implementation
return password; // Placeholder
}
}
リポジトリレイヤー
// repositories/UserRepository.ts
export class UserRepository {
async findById(id: string): Promise<User | null> {
// Database query
return db.user.findUnique({ where: { id } });
}
async create(data: CreateUserData): Promise<User> {
return db.user.create({ data });
}
async update(id: string, data: UpdateUserData): Promise<User> {
return db.user.update({
where: { id },
data,
});
}
async delete(id: string): Promise<void> {
await db.user.delete({ where: { id } });
}
}
依存性注入
基本的な DI パターン
// Composition root
const userRepository = new UserRepository();
const userService = new UserService(userRepository);
const userController = new UserController(userService);
export { userController };
サービスコンテナ
// container.ts
class Container {
private services: Map<string, any> = new Map();
register<T>(name: string, factory: () => T): void {
this.services.set(name, factory());
}
get<T>(name: string): T {
const service = this.services.get(name);
if (!service) {
throw new Error(`Service ${name} not found`);
}
return service;
}
}
export const container = new Container();
// Register services
container.register('userRepository', () => new UserRepository());
container.register('userService', () => new UserService(
container.get('userRepository')
));
container.register('userController', () => new UserController(
container.get('userService')
));
エラー処理
カスタムエラークラス
export class AppError extends Error {
constructor(
public message: string,
public statusCode: number = 500,
public isOperational: boolean = true
) {
super(message);
Object.setPrototypeOf(this, AppError.prototype);
}
}
export class NotFoundError extends AppError {
constructor(resource: string) {
super(`${resource} not found`, 404);
}
}
export class ValidationError extends AppError {
constructor(message: string) {
super(message, 400);
}
}
// Usage
async function getUser(id: string): Promise<User> {
const user = await userRepository.findById(id);
if (!user) {
throw new NotFoundError('User');
}
return user;
}
非同期エラーラッパー
type AsyncHandler = (
req: Request,
res: Response,
next: NextFunction
) => Promise<void>;
export const asyncHandler = (fn: AsyncHandler) => {
return (req: Request, res: Response, next: NextFunction) => {
Promise.resolve(fn(req, res, next)).catch(next);
};
};
// Usage
router.get('/users/:id', asyncHandler(async (req, res) => {
const user = await userService.getById(req.params.id);
res.json({ data: user });
}));
ベストプラクティス
1. 常に Async/Await を使用する
// ✅ Good: async/await
async function getUser(id: string): Promise<User> {
const user = await userRepository.findById(id);
return user;
}
// ❌ Avoid: Promise chains
function getUser(id: string): Promise<User> {
return userRepository.findById(id)
.then(user => user)
.catch(error => throw error);
}
2. レイヤーを分離する
// ✅ Good: Separated layers
// Controller handles HTTP
// Service handles business logic
// Repository handles data access
// ❌ Avoid: Business logic in controllers
class UserController {
async create(req: Request, res: Response) {
// ❌ Don't put business logic here
const hashedPassword = await hash(req.body.password);
const user = await db.user.create({...});
res.json(user);
}
}
3. すべてに型を指定する
// ✅ Good: Full type coverage
async function updateUser(
id: string,
data: UpdateUserDto
): Promise<User> {
return userService.update(id, data);
}
// ❌ Avoid: any types
async function updateUser(id: any, data: any): Promise<any> {
return userService.update(id, data);
}
追加リソース
その他のパターンについては、以下を参照してください。
- async-and-errors.md - 高度なエラー処理
- testing-guide.md - 包括的なテスト
- architecture-patterns.md - アーキテクチャの詳細
コメント
Markdown 対応