express-typescript について
保守しやすい API の構築を支援する Express.js と TypeScript の開発スキルです。型、ルーティング、検証、セキュリティ、エラー処理、テストの指針を示します。
主な機能
- 01型付き関数とインターフェース
- 02リソース別ルーティング
- 03Zod によるリクエスト検証
- 04一元化されたエラーミドルウェア
- 05セキュリティと統合テスト
活用シーン
- 01Express API のルート構成
- 02Zod によるリクエストデータの検証
- 03共通エラー処理の適用
- 04Jest と supertest による API テスト
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ mindrally/ skills --skill express-typescriptインストール後、Claude Code で/express-typescriptを入力して読み込まれたか確認します。
詳細
Express TypeScript 開発
Express.js と TypeScript の開発に精通し、スケーラブルで保守性の高い API の構築に関する深い知識があります。
TypeScript の一般的なガイドライン
基本原則
- すべてのコードとドキュメントには英語を使用します
- 変数と関数(パラメーターおよび戻り値)の型を必ず宣言します
any型の使用は避け、必要な型を作成します- 公開クラスとメソッドのドキュメントには JSDoc を使用します
- 簡潔で保守しやすく、技術的に正確なコードを書きます
- 関数型および宣言型のプログラミングパターンを使用し、可能な限りクラスを避けます
- DRY 原則に従うため、反復とモジュール化を優先します
命名規則
- 型とインターフェースには PascalCase を使用します
- 変数、関数、メソッドには camelCase を使用します
- ファイル名とディレクトリ名には kebab-case を使用します
- 環境変数には UPPERCASE を使用します
- 補助動詞を使った説明的な変数名を使用します:
isLoading、hasError、canDelete - 各関数は動詞で始めます
関数
- 単一の目的を持つ短い関数を書きます
- ミドルウェアとハンドラーにはアロー関数を使用します
- コードベース全体で一貫して async/await を使用します
- 複数のパラメーターには RO-RO パターンを使用します
型とインターフェース
- オブジェクトの形状には型よりインターフェースを優先します
- enum は避け、マップまたは const オブジェクトを使用します
- 実行時のバリデーションには Zod を使用し、型を推論します
- 不変のプロパティには
readonlyを使用します
Express 固有のガイドライン
プロジェクト構成
src/
routes/
{resource}/
index.ts
controller.ts
validators.ts
middleware/
auth.ts
errorHandler.ts
requestLogger.ts
validateRequest.ts
services/
{domain}Service.ts
models/
{entity}.ts
types/
express.d.ts
index.ts
utils/
config/
app.ts
server.ts
アプリケーションのセットアップ
import express, { Express } from 'express';
import helmet from 'helmet';
import cors from 'cors';
import { errorHandler } from './middleware/errorHandler';
import { requestLogger } from './middleware/requestLogger';
import routes from './routes';
const createApp = (): Express => {
const app = express();
// Security middleware
app.use(helmet());
app.use(cors());
// Body parsing
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
// Request logging
app.use(requestLogger);
// Routes
app.use('/api', routes);
// Error handling (must be last)
app.use(errorHandler);
return app;
};
export default createApp;
ミドルウェアのパターン
- 横断的な関心事にはミドルウェアを使用します
- 実行順にミドルウェアを連結します
- エラーは専用のエラーミドルウェアで処理します
import { Request, Response, NextFunction } from 'express';
// Request logging middleware
const requestLogger = (req: Request, res: Response, next: NextFunction): void => {
const start = Date.now();
res.on('finish', () => {
const duration = Date.now() - start;
console.log(`${req.method} ${req.path} ${res.statusCode} ${duration}ms`);
});
next();
};
// Authentication middleware
const authenticate = async (req: Request, res: Response, next: NextFunction): Promise<void> => {
try {
const token = req.headers.authorization?.split(' ')[1];
if (!token) {
res.status(401).json({ error: 'No token provided' });
return;
}
const user = await verifyToken(token);
req.user = user;
next();
} catch (error) {
res.status(401).json({ error: 'Invalid token' });
}
};
ルーティング
- リソースごとにルートを整理します
- モジュール化されたルート定義には Router を使用します
- 適切なレベルでミドルウェアを適用します
import { Router } from 'express';
import { authenticate } from '../middleware/auth';
import { validateRequest } from '../middleware/validateRequest';
import { createUserSchema, updateUserSchema } from './validators';
import * as controller from './controller';
const router = Router();
router.get('/', controller.listUsers);
router.get('/:id', controller.getUser);
router.post('/', validateRequest(createUserSchema), controller.createUser);
router.put('/:id', authenticate, validateRequest(updateUserSchema), controller.updateUser);
router.delete('/:id', authenticate, controller.deleteUser);
export default router;
リクエストのバリデーション
- すべての受信リクエストを検証します
- スキーマの定義とバリデーションには Zod を使用します
- 再利用可能なバリデーションミドルウェアを作成します
import { z } from 'zod';
import { Request, Response, NextFunction } from 'express';
const createUserSchema = z.object({
body: z.object({
name: z.string().min(1),
email: z.string().email(),
password: z.string().min(8),
}),
});
const validateRequest = (schema: z.ZodSchema) => {
return async (req: Request, res: Response, next: NextFunction): Promise<void> => {
try {
await schema.parseAsync({
body: req.body,
query: req.query,
params: req.params,
});
next();
} catch (error) {
if (error instanceof z.ZodError) {
res.status(400).json({
error: 'Validation failed',
details: error.errors,
});
return;
}
next(error);
}
};
};
エラー処理
- カスタムエラークラスを作成します
- 集中管理されたエラーハンドラーミドルウェアを使用します
- 一貫性のあるエラーレスポンスを返します
class AppError extends Error {
constructor(
public statusCode: number,
public message: string,
public code: string = 'INTERNAL_ERROR'
) {
super(message);
this.name = 'AppError';
}
}
class NotFoundError extends AppError {
constructor(resource: string) {
super(404, `${resource} not found`, 'NOT_FOUND');
}
}
// Error handler middleware
const errorHandler = (err: Error, req: Request, res: Response, next: NextFunction): void => {
if (err instanceof AppError) {
res.status(err.statusCode).json({
error: err.code,
message: err.message,
});
return;
}
console.error(err);
res.status(500).json({
error: 'INTERNAL_ERROR',
message: 'An unexpected error occurred',
});
};
TypeScript の拡張
カスタムプロパティ用に Express の型を拡張します:
// types/express.d.ts
import { User } from '../models/User';
declare global {
namespace Express {
interface Request {
user?: User;
requestId?: string;
}
}
}
セキュリティのベストプラクティス
- セキュリティヘッダーには helmet を使用します
- レート制限を実装します
- ユーザー入力をサニタイズします
- 本番環境では HTTPS を使用します
- CORS を適切に実装します
import helmet from 'helmet';
import rateLimit from 'express-rate-limit';
app.use(helmet());
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // limit each IP to 100 requests per windowMs
});
app.use('/api', limiter);
テスト
- Jest と supertest を使用して統合テストを行います
- ミドルウェアを個別にテストします
- 外部依存関係をモックします
import request from 'supertest';
import createApp from '../app';
describe('Users API', () => {
const app = createApp();
it('should create a user', async () => {
const response = await request(app)
.post('/api/users')
.send({ name: 'John', email: '[email protected]', password: 'password123' })
.expect(201);
expect(response.body).toHaveProperty('id');
expect(response.body.name).toBe('John');
});
});
コメント
Markdown 対応