Express や Koa を使用した Web アプリケーション開発において、TypeScript の型定義に関する問題は多くの開発者が直面する課題です。この記事では、一般的な型定義の問題とその解決策について、実践的なアプローチを解説します。

リクエストとレスポンスの型定義

Express での基本的な型定義は以下のように行います:

import { Request, Response } from 'express';

interface CustomRequest extends Request {
  user?: {
    id: string;
    name: string;
  };
}

app.get('/api/user', (req: CustomRequest, res: Response) => {
  const user = req.user;  // 型安全にアクセス可能
  res.json(user);
});

ミドルウェアでの型拡張

ミドルウェアで Request オブジェクトに新しいプロパティを追加する場合:

declare global {
  namespace Express {
    interface Request {
      customData: string;
    }
  }
}

const middleware = (req: Request, res: Response, next: NextFunction) => {
  req.customData = 'some data';
  next();
};

Koa での型定義

Koa を使用する場合の型定義:

import Koa from 'koa';

interface CustomState {
  user: {
    id: string;
    name: string;
  };
}

interface CustomContext extends Koa.Context {
  state: CustomState;
}

const app = new Koa<CustomState>();

app.use(async (ctx: CustomContext) => {
  const user = ctx.state.user;  // 型安全にアクセス可能
  ctx.body = user;
});

エラーハンドリングの型定義

型安全なエラーハンドリングの実装:

class CustomError extends Error {
  constructor(
    public statusCode: number,
    message: string
  ) {
    super(message);
    this.name = 'CustomError';
  }
}

app.use((err: CustomError, req: Request, res: Response, next: NextFunction) => {
  res.status(err.statusCode).json({
    error: err.message
  });
});

ジェネリック型の活用

再利用可能なコントローラーパターン:

type Controller<T> = (req: Request<T>, res: Response) => Promise<void>;

const createUser: Controller<{ name: string }> = async (req, res) => {
  const { name } = req.body;
  // 型安全な実装
};

型定義のベストプラクティス

1. 共通の型定義ファイルの作成

// types/index.ts
export interface User {
  id: string;
  name: string;
}

export interface RequestWithUser extends Request {
  user?: User;
}

2. 型ガードの活用

function isUser(obj: any): obj is User {
  return 'id' in obj && 'name' in obj;
}

3. zod や io-ts などの型バリデーションライブラリの活用

import { z } from 'zod';

const UserSchema = z.object({
  id: z.string(),
  name: z.string()
});

app.post('/api/user', (req: Request, res: Response) => {
  const result = UserSchema.safeParse(req.body);
  if (!result.success) {
    return res.status(400).json({ errors: result.error });
  }
  // 型安全な処理
});

パフォーマンスと型定義

型定義は実行時のパフォーマンスに影響を与えないことを理解することが重要です。TypeScript の型は開発時のみに存在し、コンパイル後は JavaScript として実行されます。

まとめ

Express/Koa での TypeScript 型定義は、適切に実装することで以下のメリットが得られます:

  • コード補完の強化
  • 実行時エラーの事前検出
  • メンテナンス性の向上
  • チーム開発での生産性向上

型定義を効果的に活用することで、より堅牢なWebアプリケーションの開発が可能になります。