
先日、Node.jsのExpressの静的ファイル提供機能であるexpress.staticの扱いが上手くいかずにつまづいたので、直面した問題と解決方法をメモ。
なお本記事の結論、というか話の軸は「expressの仕様をちゃんと把握しようね」なので、僕とあなたの開発環境や前提に相違点があっても、一度全文読んでみてほしい。
ややくどい記事になるので、結論を最初に述べる。
express.static関数に指定するパスは、nodeプロセスを起動するディレクトリに対して相対的です。別のディレクトリからExpressアプリケーションを実行する場合は、提供するディレクトリーの絶対パスを使用する方が安全です。
環境・前提
- Windows11
- WSL2
- Ubuntu 20.04.4 LTS
- React
- Node.js 16.14.2
- npm 8.5.0
フロントをReact、バックをNode.js(Express)という構成でWebアプリ開発をしていました。
プロジェクト全体のディレクトリ構成はこんな感じ。
Project
∟client //フロント
∟server //バック
∟index.js
∟public
∟images
∟defaultCover.jpg
∟defaultProfile.png
よって、server/index.js
にexpress.static
を記述して、public/images
に保存されてる画像ファイルを取り出そうとした。
import express from "express";
const app = express();
app.use(express.static('/public'));
(以下略)
静的アセットファイルを格納しているディレクトリの名前public
を、express.static関数に渡すことで、publicディレクトリ内に保存している静的ファイルを取り出すことができる。
Expressサーバーをlocalhostの3001番ポートで起動していた場合は、React側からhttp://localhost:3001/ファイル名
にアクセスすれば静的ファイルを取り出せる。(CORSの問題は解決している前提)
しかし僕の場合、http://localhost:3001/images/ファイル名
というURLで静的ファイルを取り出したかったので、次の一文を追加した。
app.use('/images', express.static('/images'));
express.static関数と一緒に、静的ディレクトリのマウントパスを指定することで、http://localhost:3001/images/ファイル名
というURLで静的ファイルを取り出せるようになる。ちなみにこの場合、最初に記述した'/images'はあくまで仮想パスのプレフィックス(接頭辞)なので、実際にimagesというディレクトリを用意する必要は必ずしもない。
僕の場合は、扱いたい静的ファイルが画像以外にも複数あるので、publicディレクトリ直下にimagesディレクトリを用意している。
問題・エラー
ReactからNode.jsサーバーにアクセスしてバックエンド側に保存してある画像ファイルを取り出し、Reactで扱い(画面に表示)たい。画像を取り出すためにExpressの静的ファイル提供機能express.static
を利用したが、画像は表示されずブラウザの開発者ツールを確認すると404 Not Found
が返されている。
解決策
404 Not Foundが返されるということは、RequestしたURLのコンテンツが存在していない(Node.js側で対応していないURL)わけだから、まずはURLの記述ミスを疑った。
http://localhost:3001/images/defaultCover.jpg
既述の通りNode.js側では、httt://localhost;3001/imagesというURLにアクセスすればpublic/imagesディレクトリ内の静的ファイルを取りだせるよう設定している。ので、URLの記述ミスは無い。
となるとserver/index.jsファイルの記述にミスがある可能性が高いので再度確認してみた。結論として、express.static関数に渡すディレクトリパスの書き方にミスがあった。
app.use(express.static('/public')); //←ここにミス
app.use('images', express.static('/images'));
Expressの公式ドキュメントによると、express.static関数に指定するディレクトリパスは、nodeプロセスを起動するディレクトリに対して相対的である必要があるらしい。
この場合、publicディレクトリはseverディレクトリ直下にあるので、serverディレクトリでNode.jsを起動node index.js
していれば問題なく動作する。
しかしながら僕の場合は、プロジェクトのルートディレクトリProject
からNode.jsを起動node server/index.js
をしていたためエラーとなった。
よって解決策として以下のいずれかを施すと良い。
- 相対的に正しいパスを記述する
- 絶対パスを取得して記述する
1.相対的に正しいパスを記述する
app.use(express.static('server/public'));
app.use('/images', express.static('/images'));
2.絶対パスを取得して記述する
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
app.use(express.static(__dirname+'/public'));
app.use('/images', express.static('/images'));
CJSを使用していれば、最初の2行を記述する必要はない。__dirname(グローバル変数)を書くだけでファイルが格納されているディレクトリの絶対パスを取得できるからである。
しかし、ESMを使用している場合はグローバル変数を利用できない。よって、最初の2行を記述することでCJSのグローバル変数を模倣して、疑似的に絶対パスを取得している。
Node.jsには「CommonJS」と「ES Modules」という2つのモジュールがあり、デフォルトではCommonJSが採用されている。その場合、ファイルの拡張子を.js .cjsとした場合はCommonJSが、.mjsとした場合はES Moduleが適用される。
ただし、package.jsonにて"type":"module"を記述すれば、.js .mjsファイルをES Modulesとして扱えるようになる。