1. 序論
npm run dev でサーバーを起動し、curl ... | jq ... でAPIの形を確認したのに 何も表示されない。初心者だけでなく経験者でもハマりやすい落とし穴です。今回は、実際に遭遇したケースをもとに、原因の切り分け→修正→動作確認までを一気に整理します。結論から言うと、主因は サーバーが待受前に落ちていた(DB接続失敗) と IPv4/IPv6 の食い違い でした。この記事では、中学生でもイメージできるように、ネットワークを「家の呼び鈴(ポート)」「住所(IP)」にたとえて説明します。
2. 本論
2-1. まず全体像:よくある原因4つ
- サーバーが待受していない
「呼び鈴(ポート)」自体が鳴らない状態です。で誰も3000番に座っていない=リスンしていないのが一発でわかります。 - IPv4/IPv6 のズレ
localhost は環境によって ::1(IPv6)に解決されることがあります。サーバーが 127.0.0.1(IPv4)だけで待受すると、localhost には繋がりません。接続先は住所(IP)を明示してあげましょう。 - DB接続エラーで即終了
app.listen() より後でDB接続に失敗→process.exit(1) でサーバーが即死。ログでは「立っているように見えて」すぐ消える、という紛らわしい症状になります。Mongooseの接続は成功してから listen するのが王道です。 - パスやJSON構造の勘違い
ルートが /api/todos なのに /todos を叩く、JSONが items ではなく data/meta なのに jq で .items を参照してしまう、など。Next.jsのAPIは /api/... 配下です。
たとえ話:
家(サーバー)が開いていても、呼び鈴(ポート)が鳴らなければ反応は返りません。住所(IP)が違っても当然届かないし、訪問先(パス)が違えば目的の人(エンドポイント)には会えません。
2-2. 最短の切り分けコマンド(コピペOK)
- A. とにかく“エラーを見える化”(-s はいったん封印)
curl -v -m 5 http://127.0.0.1:3000/ # 詳細ログ + 5秒タイムアウト
- B. 今、誰がベルを見張ってる?(3000番のLISTEN)
lsof -nP -iTCP:3000 -sTCP:LISTEN
何も出なければ 誰も待受していません。
- C. IPv4/IPv6の両面チェック
curl -v -m 5 http://127.0.0.1:3000/
curl -v -m 5 http://[::1]:3000/
どちらか片方だけ通るなら サーバーのlistenアドレス を合わせます。
- D. 代表的なポートを一括確認
for p in 3000 3001 5173 8080 4321 8787 4200 5000; do
echo "---- $p ----"
curl -I -m 2 -sS http://127.0.0.1:$p/ || true
done
- E. JSONの形を必ず目視
curl -sS http://127.0.0.1:3000/todos | jq # 全体を整形して見る
curl -sS http://127.0.0.1:3000/todos | jq 'keys'
jq の基本は公式マニュアルがわかりやすいです。
- F. curl の基礎オプションは公式で
-v(詳細)/-sS(静かだがエラーは表示)などは、公式「everything curl」やmanページを参照。
2-3. サーバー側の“落ちない起動”にする(Express + Mongoose)
問題点:DB接続に失敗したら process.exit(1) で即終了、listen が生き残らない。
解法:DB接続が成功してから app.listen()。ついでに IPv4へバインド、/health ルート、起動時エラーを可視化。
// server/server.js (CommonJS)
require('dotenv').config();
const express = require('express');
const cors = require('cors');
const { connectDB } = require('./config/db');
const todosRouter = require('./routes/todos');
const errorHandler = require('./middlewares/error');
const app = express();
const PORT = process.env.PORT || 3000;
const HOST = '127.0.0.1'; // まずはIPv4に固定(安定動作)
// 最小の可観測性
app.use((req, _res, next) => { console.log(req.method, req.url); next(); });
app.get('/health', (_req, res) => res.json({ ok: true }));
app.use(cors());
app.use(express.json());
app.use('/todos', todosRouter);
app.use(errorHandler);
// 起動時の未処理エラーも表示
process.on('unhandledRejection', (r) => console.error('UNHANDLED REJECTION:', r));
process.on('uncaughtException', (e) => { console.error('UNCAUGHT EXCEPTION:', e); process.exit(1); });
async function start() {
try {
await connectDB(); // ✅ 成功してから listen
const server = app.listen(PORT, HOST, () => {
const addr = server.address();
console.log(`Listening on ${addr.address}:${addr.port} (${addr.family})`);
});
server.on('error', (err) => console.error('Server listen error:', err));
} catch (err) {
console.error('Startup error (DB connection failed):', err);
process.exit(1);
}
}
start();
- Expressのミドルウェア/エラーハンドリングは公式ガイド準拠です
- Mongooseは127.0.0.1 を推奨(Node 18+ は localhost が IPv6 優先のため)
2-4. 動作確認(POST→GET→検索・ソート)
- A. ダミーTODOを追加
curl -s -X POST http://127.0.0.1:3000/todos \
-H "Content-Type: application/json" \
-d '{"title":"テストタスク","status":"pending","tags":["work"]}' | jq
- B. 一覧を取得(トップレベル形の確認)
curl -s http://127.0.0.1:3000/todos | jq
# 期待されるキー: items/page/limit/total/pages/sort/filters ...
- C. 仕様ごとのスモークテスト
# 形チェック
curl -s "http://127.0.0.1:3000/todos?limit=5&page=1" \
| jq '.items|length,.page,.limit,.total,.pages'
# フィルタ
curl -s "http://127.0.0.1:3000/todos?status=pending&limit=3" \
| jq -r '.items[].status'
# タグ(OR)
curl -s "http://127.0.0.1:3000/todos?tag=work,urgent&limit=10" \
| jq -c '.items[].tags'
# タイトル検索 & ソート(昇順)
curl -s "http://127.0.0.1:3000/todos?q=readme&sort=createdAt:asc&limit=10" \
| jq -r '.items[].title'
補足:Next.jsなどAPIが /api/... の場合はベースパスを置き換えてください。
Next.js
3. まとめ
- サーバーが待受していないと curl は沈黙します。まず lsof で LISTENの有無、curl -v で 接続可否 を可視化しよう
- localhost の解決先は環境で異なります。127.0.0.1 / [::1] を明示して確認するのが近道
- Mongoose接続は 成功後に app.listen()。/health で疎通を固定化し、起動時エラーは必ずログに出すようにしよう
- JSONの「形」を jq でまず目視。そのうえで .items などのパスを合わせれば、仕様テストが安定します
4. 参考情報(情報元)
- Mongoose — Connections(localhost より 127.0.0.1 推奨の注意).
- Mongoose — Getting Started / Connection API.
- Express — Error handling / API リファレンス / JSONミドルウェア.
- Next.js — API Routes(APIは /api/... 配下).
- lsof でLISTEN中のプロセスを調べる(macOSの実例).
- IPv4 127.0.0.1 と IPv6 ::1 の違い(localhost解決の注意).
- everything curl / curl man(-v/-sS などの基本オプション).
- jq 公式マニュアル(フィルタ・keys の使い方).