* 目次 [#cbd66de7]
#contents

** 【実践Tips】Node.jsで"レスポンス切替型"モックAPIを超シンプルに作る方法 [#rd838e8f]

*** はじめに [#j7534979]

API連携のテストやフロントエンド開発で「異常系レスポンスも手軽に試したい」「POSTMANやPRISMだとちょっと重い…」と感じたことはありませんか?

そんな時に便利なのが「ファイルでレスポンス内容を切り替えられるモックAPIサーバー」です。Node.js+Expressだけで、''たった1ファイル''で実装でき、curlや自動テストからも柔軟に制御できます。

*** どんなことができる? [#t417181c]

- レスポンス内容を responses/ 配下のJSONファイルで管理
- /setResponse/:filename で返却内容を即時切替(curl一発!)
- 利用可能なレスポンスファイル一覧もAPIで取得
- テスト自動化やCI/CDにも組み込みやすい
- PRISMやPOSTMANのようなOpenAPI依存・GUI不要

*** サンプル実装(server.js全文) [#r34e3a2c]

 const express = require('express');
 const cors = require('cors');
 const fs = require('fs');
 const path = require('path');
 
 const app = express();
 const PORT = 3000;
 
 // 現在のレスポンスファイル名(デフォルト)
 let currentResponseFile = 'normal.json';
 const responseDir = path.join(__dirname, 'responses');
 
 // ミドルウェア
 app.use(cors());
 app.use(express.json());
 
 // リクエストログ
 app.use((req, res, next) => {
     console.log(`${new Date().toISOString()} - ${req.method} ${req.path}`);
     console.log('Request Body:', JSON.stringify(req.body, null, 2));
     next();
 });
 
 // レスポンス切替API
 app.get('/setResponse/:filename', (req, res) => {
     const filename = req.params.filename;
     const filePath = path.join(responseDir, filename);
     let fileList = [];
     try {
         fileList = fs.readdirSync(responseDir).filter(f => f.endsWith('.json'));
     } catch (e) {
         fileList = [];
     }
     if (!fs.existsSync(filePath)) {
         return res.status(400).json({ 
             result: 'NG', 
             message: `ファイルが存在しません: ${filename}`, 
             availableFiles: fileList 
         });
     }
     currentResponseFile = filename;
     res.json({ 
         result: 'OK', 
         message: `次回以降のレスポンスを ${filename} に設定しました。`, 
         availableFiles: fileList 
     });
 });
 
 // ファイルからレスポンスを読み込む
 function loadMockResponse(filename) {
     try {
         const filePath = path.join(responseDir, filename);
         if (!fs.existsSync(filePath)) return null;
         const data = fs.readFileSync(filePath, 'utf8');
         return JSON.parse(data);
     } catch (e) {
         console.error('レスポンスファイル読込エラー:', e);
         return null;
     }
 }
 
 // メインAPI(例:POST)
 app.post('/api/users', (req, res) => {
     const { username, password } = req.body;
 
     // 認証チェックもファイルで切替可能
     if (!username || !password) {
         const authError = loadMockResponse(currentResponseFile);
         if (authError && authError.body) {
             return res.status(authError.httpStatus || 200).json(authError.body);
         }
         return res.json({
             resultCode: "9",
             resultMessage: "認証情報が不正です"
         });
     }
 
     // メインレスポンス
     const mockResponse = loadMockResponse(currentResponseFile);
     if (mockResponse && mockResponse.body) {
         console.log('Response:', JSON.stringify(mockResponse.body, null, 2));
         return res.status(mockResponse.httpStatus || 200).json(mockResponse.body);
     }
 
     // デフォルト or フォールバック
     const defaultNormal = loadMockResponse('normal.json');
     if (defaultNormal && defaultNormal.body) {
         return res.status(defaultNormal.httpStatus || 200).json(defaultNormal.body);
     }
     res.json({ resultCode: "0", resultMessage: "", userList: [] });
 });
 
 // ヘルスチェック
 app.get('/health', (req, res) => {
     res.json({ status: 'OK', timestamp: new Date().toISOString() });
 });
 
 // サーバー起動
 app.listen(PORT, () => {
     console.log(`🚀 モックサーバー起動: http://localhost:${PORT}`);
 });

*** レスポンスファイル例 [#t347bef1]

**** responses/normal.json(正常系) [#l57bb328]

 {
     "httpStatus": 200,
     "body": {
         "resultCode": "0",
         "resultMessage": "正常終了",
         "userList": [
             {
                 "id": "001",
                 "name": "サンプルデータ1",
                 "status": "active"
             },
             {
                 "id": "002", 
                 "name": "サンプルデータ2",
                 "status": "inactive"
             }
         ]
     }
 }

**** responses/error500.json(エラー系) [#tb991e4f]

 {
     "httpStatus": 500,
     "body": {
         "resultCode": "5",
         "resultMessage": "内部サーバーエラーが発生しました",
         "errorDetails": "データベース接続に失敗しました"
     }
 }

**** responses/auth_error.json(認証エラー) [#m9ff4b77]

 {
     "httpStatus": 401,
     "body": {
         "resultCode": "9",
         "resultMessage": "認証に失敗しました",
         "errorCode": "AUTH_FAILED"
     }
 }

*** 使い方 [#zcef7a12]

**** 1. プロジェクトセットアップ [#re8ba63e]

 # プロジェクトディレクトリ作成
 mkdir mock-api-server
 cd mock-api-server
 
 # package.json初期化
 npm init -y
 
 # 必要なパッケージインストール
 npm install express cors
 
 # レスポンスファイル用ディレクトリ作成
 mkdir responses

**** 2. サーバー起動 [#u4717a5c]

 node server.js

**** 3. レスポンス切替 [#q2c0f40c]

memo: jqコマンドを入れると、json整形してくれて見やすくなります。

 # 正常系レスポンスに切替
 curl http://localhost:3000/setResponse/normal.json | jq .
 
 # エラー系レスポンスに切替
 curl http://localhost:3000/setResponse/error500.json | jq .
 
 # 認証エラーに切替
 curl http://localhost:3000/setResponse/auth_error.json | jq .

**** 4. メインAPIの呼び出し [#le3c6ac4]

 # POSTリクエストでAPIテスト
 curl -X POST http://localhost:3000/api/users \
   -H "Content-Type: application/json" \
   -d '{"username": "testuser", "password": "password123"}' | jq .

**** 5. 利用可能なファイル一覧確認 [#u28b5c22]

 # 存在しないファイルを指定すると、利用可能ファイル一覧が返る
 curl http://localhost:3000/setResponse/dummy.json | jq .

*** どんな時に便利? [#h4f7fd4c]

**** 異常系・境界値テストをすぐ試したいとき [#ya05a0d0]
従来のモックツールでは設定が面倒だった異常系レスポンスも、JSONファイルを作成してcurl一発で切り替えられます。

**** フロントエンドや結合テストでAPIの返却値を柔軟に変えたいとき [#f95e4040]
UI開発中に「エラー画面の表示確認をしたい」「データが空の場合の動作を見たい」といったニーズに即座に対応できます。

**** OpenAPI仕様が未確定またはPRISM/POSTMANだと重い・運用が面倒なとき [#l97050b1]
仕様策定中のプロジェクトでも、必要最小限の実装で素早くモック環境を構築できます。

**** CI/CDの自動テストでレスポンスパターンをプログラムから切り替えたいとき [#rde837c3]
テストスクリプト内でレスポンス切替APIを呼び出すことで、様々なシナリオのテストを自動化できます。

*** さらなる活用Tips [#i23bb945]

みんなの、プロジェクトに合わせてカスタマイズしていくアイデアというかヒント

**** パターン別ディレクトリ管理 [#md49e256]

 responses/
 ├── normal/
 │   ├── success.json
 │   └── empty_list.json
 ├── error/
 │   ├── 400_bad_request.json
 │   ├── 401_unauthorized.json
 │   ├── 500_server_error.json
 │   └── timeout.json
 └── edge_case/
     ├── large_data.json
     └── special_characters.json

**** 環境変数での設定 [#r222da9f]

 const PORT = process.env.PORT || 3000;
 const RESPONSE_DIR = process.env.RESPONSE_DIR || path.join(__dirname, 'responses');

**** レスポンス遅延の実装 [#nb679ec7]

 // レスポンスファイルに遅延設定を追加
 {
     "httpStatus": 200,
     "delay": 2000,  // 2秒遅延
     "body": { ... }
 }
 
 // サーバー側で遅延処理
 if (mockResponse.delay) {
     setTimeout(() => {
         res.status(mockResponse.httpStatus || 200).json(mockResponse.body);
     }, mockResponse.delay);
     return;
 }

*** まとめ [#zdfe8696]

Express+ファイル操作だけで、超軽量・高拡張性なモックAPIが作れます。GUI不要・OpenAPI不要・curl一発で切替可能なこのアプローチは、「現場で今すぐ動かしたい」「自動テストを強化したい」プロジェクトに最適です。

シンプルな構成でありながら、実際の開発・テスト現場で求められる柔軟性を兼ね備えているため、チーム開発の生産性向上に大きく貢献できるでしょう。

*** 参考 [#d834b007]

- レスポンスファイルの仕様やcurl例は、README.mdにもまとめておくと便利です
- 本記事のコードは自由にコピペ&カスタマイズOK!

他にも「こういう機能追加したい」「もっと高度な分岐が欲しい」などあれば、ぜひコメントやフィードバックください!

ご参考になれば幸いです。
トップ   編集 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS