mcpField
E
express-typescript

express-typescript

github.com/mindrally開発2871.4k 回インストール3 週間前に更新
リポジトリ

express-typescript について

保守しやすい API の構築を支援する Express.js と TypeScript の開発スキルです。型、ルーティング、検証、セキュリティ、エラー処理、テストの指針を示します。

主な機能

  1. 01型付き関数とインターフェース
  2. 02リソース別ルーティング
  3. 03Zod によるリクエスト検証
  4. 04一元化されたエラーミドルウェア
  5. 05セキュリティと統合テスト

活用シーン

  1. 01Express API のルート構成
  2. 02Zod によるリクエストデータの検証
  3. 03共通エラー処理の適用
  4. 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');
  });
});

コメント