Express で「Cannot GET /パス」と表示されルートにアクセスできない
「Cannot GET /パス」は、メソッドとパスに一致するルートが無いときに Express が返す 404。
POST のルートをブラウザで開いた、Router のマウントパスの二重指定、express.static の相対パスのずれ、SPA のパスの直接表示が主な原因だ。
公開:
要約
ブラウザに次の 1 行だけが表示されるのは、Express がすべてのミドルウェアとルートを順に試し、どれも応答しなかったときに返す既定の 404 だ。
Cannot GET /about公式 FAQ(新しいタブで開く) のとおり、Express では 404 はエラーとして扱われないので、エラー処理のミドルウェアには届かない。
表示の GET はメソッド、その後ろは Express が受け取ったパスなので、まずこの 2 つを登録したルートと突き合わせる。
よくある原因
- メソッドが違う:
app.post('/users', ...)だけを定義し、ブラウザで/usersを開くと GET で届くのでCannot GET /usersになる。 - マウントパスが二重になっている:
app.use('/api', router)でマウントしたルーターの中でrouter.get('/api/items', ...)と書くと、実際の URL は/api/api/itemsになり、/api/itemsは 404 になる。
ルーターのパスは、マウントパスを取り除いた残りと照合される。 - 静的ファイルのディレクトリがずれている: 公式ガイド(新しいタブで開く)にあるとおり、
express.static('public')の相対パスはサーバーのファイルの場所ではなく、nodeを起動したディレクトリを基準に解決される。
プロジェクトのルートでnode src/server.jsと起動すると、src/publicではなく./publicを探す。
ファイルが無いとexpress.staticは次の処理へ渡すので、最後にCannot GETが返る。 - SPA のパスを直接開いている: React Router や Vue Router のパスはブラウザの中で切り替わるだけで、サーバー側にルートは無い。
トップから遷移すると表示できるのに、/aboutをリロードするとCannot GET /aboutになる。
解決策
1. メソッドとパスを確かめる
curl -i -X POST http://localhost:3000/usersPOST のルートは、curl や fetch() で POST を送って確かめる。
2. ルーターのパスはマウントパスを除いて書く
const router = express.Router();
router.get("/items", (req, res) => res.json([])); // /api/items で応答する
app.use("/api", router);3. 静的ファイルは絶対パスで指定する
const path = require("path");
app.use(express.static(path.join(__dirname, "public")));__dirname はそのファイルがあるディレクトリなので、起動した場所に左右されない。
ES Modules で __dirname が使えない場合は Node.js の ESM で __dirname が使えない場合の対処を参照。
4. SPA はフォールバックで index.html を返す
const path = require("path");
const dist = path.join(__dirname, "dist");
apiRouter.use((req, res) => res.status(404).json({ error: "not found" })); // API の 404
app.use("/api", apiRouter); // API を先に登録する
app.use(express.static(dist));
app.get("/{*splat}", (req, res) => {
res.sendFile(path.join(dist, "index.html"));
});Express 5 ではパスに名前の無い * を書くと起動時に例外になるので、トップの / も含めてすべてに一致させるには /{*splat} と書く(Express 4 では *)。
フォールバックは存在しない /api/ のパスにも index.html を返すので、API のルーターの末尾には 404 を返すミドルウェアを置いておく。