「エンドポイントとは」
- サーバーが外部に公開する「機能の窓口」(URLごとに役割が決まっている)
- URLパスで区別され、同じポート上に複数置ける
- 返すものは用途次第:HTML(画面)/JSON(データAPI)など
- ブラウザやフロントJSがリクエストを送り、サーバー側の関数が処理してレスポンスを返す
- 一つのアプリの中に複数のエンドポイントを作り、役割を分担するのが普通
👉 要するに「Webアプリの各機能にアクセスするための住所(URL)がエンドポイント」ということ
はじめに
チャットボット本体と、その利用状況を監視する管理ツールを作っていたときに、どうしても引っかかったのが「エンドポイントって何なの?」という疑問でした。
シロート感覚では「HTMLファイルがある場所?」「ポートを分ける必要があるの?」と考えてしまいます。ここでは実際に作った /admin 管理画面の実装を例に、エンドポイントをかみ砕いて整理します。
エンドポイントとは何か
エンドポイントとは、サーバーが外部に公開している“データや機能の窓口”のことです。
- 住所(URL)ごとに役割が決まっている
- リクエストを受け取ると、裏で対応する関数が実行され、結果が返ってくる
- 結果は HTML の場合もあれば、JSON の場合もある
つまり「アドレス参照=Webを通した関数呼び出し」というイメージです。
管理画面で用意した3つのエンドポイント
管理ツールでは「ユーザー → 会話リスト → 会話の中身」という3段階が必要でした。そのために次の3つの窓口を用意しました。
/admin/api/users- ユーザー一覧を返す窓口
- 例:
[{id:"u1", name:"ONO"}, {id:"u2", name:"ITO"}]
/admin/api/conversations?user_id=...- 特定ユーザーの会話リストを返す窓口
- 例:
[{id:"c101", started_at:"2025-09-10"}, ...]
/admin/api/messages?conversation_id=...- 会話の中身を返す窓口
- 例:
[{role:"user", text:"体験できますか"}, {role:"bot", text:"可能です"}]
こう分けることで、必要なデータだけを順に取りに行けて効率的です。
よくある誤解と正しい理解
❌ 誤解1:エンドポイント=HTMLファイル
実際には、HTMLを返すエンドポイントもあれば、JSONを返すエンドポイントもあります。
/admin→admin.htmlを返す(管理画面UI)/admin/api/...→ JSONを返す(データ)
すべてが index.html に対応しているわけではありません。
❌ 誤解2:エンドポイントごとにポートが必要
ポートはアプリケーションごとに使われるものです。
同じアプリ(FastAPI + Uvicorn)であれば、1つのポート (:8000など) に複数のエンドポイントをぶら下げられます。
例:
http://localhost:8000/http://localhost:8000/adminhttp://localhost:8000/admin/api/users
まとめ
エンドポイントは「アプリの機能にアクセスするための窓口」です。
- HTMLページを返すこともあれば、JSONデータを返すこともある
- ポートは共通で、パスを変えることで複数の役割を実現できる
- 管理ツールでは「ユーザー一覧」「会話リスト」「会話の中身」の3つを窓口に分けることで、効率的な設計ができた
シロートとして始めたときは「index.htmlが3つあるイメージ」や「ポートを分ける必要があるの?」と考えていましたが、実際には1つのアプリの中に複数のエンドポイントを置いて使い分けるのが自然でした。
付録
エンドポイント実装例:①HTMLを返す場合 ②JSONを返す場合
① HTMLを返す(サーバーレンダリング)
流れ:ブラウザが /admin をGET → サーバがテンプレにデータを差し込んで 完成HTML を返す。
FastAPI(サーバ)
from fastapi import FastAPI, Request
from starlette.templating import Jinja2Templates
from starlette.responses import HTMLResponse
app = FastAPI()
templates = Jinja2Templates("app/templates")
@app.get("/admin", response_class=HTMLResponse)
def admin_page(request: Request):
ctx = {"request": request, "admin_name": "admin", "unread_count": 3}
return templates.TemplateResponse("admin.html", ctx)
Jinja2テンプレート(admin.html)
<h1>管理画面</h1>
<p>ようこそ、{{ admin_name }} さん</p>
<p>未読: {{ unread_count }} 件</p>
HTTPのやり取り(ざっくり)
- Request:
GET /admin - Response:
200 text/html(サーバでデータを埋め込んだ完成HTML)
② JSONを返す(APIエンドポイント)
流れ:フロントJSが /admin/api/messages?conversation_id=123 をfetch → サーバは JSON を返す → 受け取ったJSがDOMを更新。
FastAPI(サーバ)
from fastapi import Query
@app.get("/admin/api/messages")
def list_messages(conversation_id: str = Query(...)):
return {
"conversation_id": conversation_id,
"messages": [
{"role": "user", "text": "体験できますか?"},
{"role": "bot", "text": "可能です。候補日を教えてください。"}
]
}
フロント(admin.js の一部)
async function loadMessages(convId){
const res = await fetch(`/admin/api/messages?conversation_id=${encodeURIComponent(convId)}`);
const data = await res.json(); // ← JSONを受け取る
const box = document.querySelector("#chat");
box.innerHTML = data.messages.map(m =>
`<div class="bubble ${m.role}">${m.text}</div>`
).join("");
}
curlで叩く例(開発時の確認用)
curl "http://localhost:8000/admin/api/messages?conversation_id=123"
# => {"conversation_id":"123","messages":[{"role":"user","text":"体験できますか?"}, ...]}
HTTPのやり取り(ざっくり)
- Request:
GET /admin/api/messages?conversation_id=123 - Response:
200 application/json(生データ)
使い分けのコツ
- HTML:最初の画面表示や固定レイアウトに強い(早い・簡単)
- JSON:動的更新・検索・フィルタなど“画面の一部だけ差し替え”に強い
この2つを組み合わせると、いま作っている管理画面みたいな「Jinja2ベース+部分更新はfetch」が綺麗にハマります。


コメント