06. Web APIサーバー実装

この資料は 2025 年 11 月時点での田中正吾の雑感として書いたものです。あくまでもハッカソンでザっと AI の連携を作れるようにするために、動かして進められるような説明用の資料とサンプル群を用意した次第です。

標準入力の対話型アプリを Web API サーバーとして実装しましょう。

学べること

  • Express を使った Web サーバーの基本
  • REST API の設計
  • 継続会話をサーバー環境で実装する方法
  • 状態管理(会話履歴、メモデータ)
  • HTTP エンドポイントの設計パターン

準備するもの

  • Node.js (v18 以上)
  • OpenAI API キー
  • 05 の理解

セットアップ

サンプルコードは本教材の Codespace(リンク集を参照)の 06_interactive-chat-server/ にあります。

フォルダ移動

まず、ターミナルに表示されているカレントディレクトリ(現在の作業フォルダ)を確認しましょう。

  • /workspaces/hackathon-ai-and-mcp-sample-202511-codespace/ の場合は、以下のコマンドでフォルダ移動します。
cd 06_interactive-chat-server
  • 続けて 05 から勉強している場合は、カレントディレクトリが /workspaces/hackathon-ai-and-mcp-sample-202511-codespace/05_interactive-chat で一つ階層が深いので、以下のコマンドでフォルダ移動します。
cd ../06_interactive-chat-server

手元の Visual Studio Code で作業されている方は、現在作業中のカレントディレクトリを確認いただいて上記を参考にフォルダを移動しましょう。

※フォルダ移動について詳しくは「Codespace でのフォルダ移動」を参照してください。

インストール

カレントディレクトリの移動が確認できたら、関連ライブラリをインストールして準備します。

npm install

これ以降はこのカレントフォルダでコマンドを打って作業していきましょう。

API キーの設定

各サンプルファイルの先頭にある API キー設定箇所を、お使いの OpenAI API キーに書き換えてください。

01_weather-interactive-chat-server.js の場合:

// OpenAI API キー
const OPENAI_API_KEY = "ここにあなたのAPIキーを入力";

02_memo-interactive-chat-server.js の場合:

// OpenAI API キー
const OPENAI_API_KEY = "ここにあなたのAPIキーを入力";

サンプル一覧

天気チャットサーバー

ファイル: 01_weather-interactive-chat-server.js

node 01_weather-interactive-chat-server.js

05 の 01_weather-interactive-chat.js を Web API サーバー化したものです。

エンドポイント:

  • POST /message - メッセージを送信(会話継続)
  • POST /clear - 会話履歴をクリア
  • GET / - サーバー情報取得

使い方(別のターミナルで):

# メッセージ送信
curl -X POST http://localhost:3000/message \
  -H "Content-Type: application/json" \
  -d '{"message":"東京の天気を教えて"}'

# 履歴クリア
curl -X POST http://localhost:3000/clear

# サーバー情報
curl http://localhost:3000/

メモチャットサーバー

ファイル: 02_memo-interactive-chat-server.js

node 02_memo-interactive-chat-server.js

05 の 02_memo-interactive-chat.js を Web API サーバー化したものです。継続会話 + ツール + サーバー化の完全実装例です。

エンドポイント:

  • POST /message - メッセージを送信(会話継続)
  • POST /clear - 会話履歴をクリア(メモは保持)
  • GET / - サーバー情報とメモ一覧取得

使い方(別のターミナルで):

# メモを追加
curl -X POST http://localhost:3001/message \
  -H "Content-Type: application/json" \
  -d '{"message":"買い物リストを追加"}'

# さらに追加(会話が継続されている)
curl -X POST http://localhost:3001/message \
  -H "Content-Type: application/json" \
  -d '{"message":"牛乳も追加"}'

# メモを表示
curl -X POST http://localhost:3001/message \
  -H "Content-Type: application/json" \
  -d '{"message":"メモを見せて"}'

# 会話履歴クリア(メモは残る)
curl -X POST http://localhost:3001/clear

# サーバー情報(メモ一覧も表示)
curl http://localhost:3001/

実装パターン

Express サーバーの基本

const express = require("express");
const app = express();
app.use(express.json()); // JSON 解析

// エンドポイント定義
app.post("/message", async (req, res) => {
  const { message } = req.body;
  // 処理...
  res.json({ response: "..." });
});

// サーバー起動
app.listen(3000, () => {
  console.log("サーバー起動: http://localhost:3000");
});

express.json() で JSON リクエストボディをパースし、app.post() で POST エンドポイントを定義します。レスポンスは res.json() で JSON 形式で返します。

会話履歴の保持

// サーバー起動中はメモリに保持
let messages = [];

app.post("/message", async (req, res) => {
  // ユーザーメッセージを履歴に追加
  messages.push({ role: "user", content: req.body.message });

  // API 呼び出し(履歴全体を送る)
  const completion = await openai.chat.completions.create({
    messages: messages,
    model: "gpt-4o-mini"
  });

  // 返答も履歴に追加
  messages.push({
    role: "assistant",
    content: completion.choices[0].message.content
  });

  res.json({ response: completion.choices[0].message.content });
});

サーバー起動中は messages 配列に会話履歴を保持します。サーバーを再起動すると履歴は失われます。

会話履歴のクリア

app.post("/clear", (req, res) => {
  messages = [];
  res.json({ message: "会話履歴をクリアしました" });
});

会話をリセットするエンドポイントを提供します。

ツール実行フロー(サーバー版)

app.post("/message", async (req, res) => {
  // 1. ユーザーメッセージを履歴に追加
  messages.push({ role: "user", content: req.body.message });

  // 2. API 呼び出し
  const completion = await openai.chat.completions.create({
    messages: messages,
    model: "gpt-4o-mini",
    tools: tools,
    tool_choice: "auto"
  });

  // 3. ツール実行がある場合
  if (completion.choices[0].message.tool_calls) {
    messages.push(completion.choices[0].message);

    for (const toolCall of completion.choices[0].message.tool_calls) {
      const result = executeTool(toolCall.function.name, ...);
      messages.push({
        role: "tool",
        tool_call_id: toolCall.id,
        content: result
      });
    }

    // 4. もう一度 API 呼び出し
    const finalCompletion = await openai.chat.completions.create({
      messages: messages,
      model: "gpt-4o-mini"
    });

    messages.push({
      role: "assistant",
      content: finalCompletion.choices[0].message.content
    });
  }

  // 5. レスポンス返却
  res.json({ response: "..." });
});

05 のツール実行パターンを、サーバー環境で実装します。

05 との違い

05_interactive-chat

  • インターフェース: 標準入力(readline)
  • 実行方法: ターミナルで対話
  • 会話管理: ローカル変数
  • 用途: CLI ツール

06_interactive-chat-server

  • インターフェース: HTTP API(Express)
  • 実行方法: curl/Postman でリクエスト
  • 会話管理: サーバーメモリ(本番は DB)
  • 用途: Web アプリのバックエンド

05 はターミナルで直接対話しましたが、06 では HTTP API としてアクセスします。

コードの説明

完全な実装例

const express = require("express");
const OpenAI = require("openai");

const OPENAI_API_KEY = "your-api-key";
const openai = new OpenAI({ apiKey: OPENAI_API_KEY });

const app = express();
app.use(express.json());

// 会話履歴
let messages = [
  { role: "system", content: "あなたは親切なアシスタントです。" },
];

// メッセージ送信エンドポイント
app.post("/message", async (req, res) => {
  try {
    const { message } = req.body;

    // ユーザーメッセージを追加
    messages.push({ role: "user", content: message });

    // API 呼び出し
    const completion = await openai.chat.completions.create({
      messages: messages,
      model: "gpt-4o-mini",
    });

    // アシスタントの返答を追加
    const assistantMessage = completion.choices[0].message;
    messages.push(assistantMessage);

    res.json({ response: assistantMessage.content });
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

// 履歴クリアエンドポイント
app.post("/clear", (req, res) => {
  messages = [
    { role: "system", content: "あなたは親切なアシスタントです。" },
  ];
  res.json({ message: "会話履歴をクリアしました" });
});

// サーバー情報エンドポイント
app.get("/", (req, res) => {
  res.json({
    name: "Chat API Server",
    endpoints: ["/message", "/clear", "/"],
  });
});

// サーバー起動
app.listen(3000, () => {
  console.log("サーバー起動: http://localhost:3000");
});

このコードを実行すると、HTTP API サーバーが起動します。

知っておくと良いこと

エラーハンドリング

サーバーでは、エラーが発生しても停止しないようにエラーハンドリングを入れておくと安心です。

app.post("/message", async (req, res) => {
  try {
    // 処理...
  } catch (error) {
    console.error("エラー:", error);
    res.status(500).json({ error: error.message });
  }
});

エラーが起きた場合でも、適切なステータスコードとメッセージを返すことができます。

CORS 対応(フロントエンドから呼び出す場合)

フロントエンド(ブラウザ)から API を呼び出す場合、CORS 設定が必要です。

npm install cors
const cors = require("cors");
app.use(cors());

これで、どのドメインからでもアクセスできるようになります。

本番環境への展開のときの考慮事項

現在の実装はシンプルな学習用です。本番環境では以下の対応が必要です:

  1. 会話履歴の永続化

    • Redis, MongoDB などに保存
    • ユーザーごとにセッション管理
  2. 認証・認可

    • API キー、JWT などで保護
  3. エラーハンドリング

    • リトライ処理
    • タイムアウト対策
  4. レート制限

    • 過度なリクエストを防止
  5. ログとモニタリング

    • アクセスログ
    • エラー追跡

ハッカソンでは、まずはシンプルな実装で動かしてみましょう。必要に応じて拡張していけます。

次のステップ

Web API サーバーとしての実装ができましたね!次は MCP を使った Claude Desktop 連携を見ていきましょう。