Express 4.x が対象です。express-generator
による生成、yarn start
の意味、express.static()
の意味、path.join()
の意味についてまで。記述しています。
「余談」とある項目はやや踏みこんだ内容になっています。必要に応じて読み飛ばしてくだい。(むしろ余談のほうが多いね…)
生成
下記のコマンドで app_name
以下に雛形を生成できる。
$ express app_name --view=pug
--view=pug
を付けない場合、views
以下のファイルの拡張子が .jade
になる。jade は pug に改名される予定(というか改名された)ので、今後は .pug
に統一されていく(はず)。なので、これから express
のアプリを作るなら --view=pug
を付けて生成したほうが良さそう。
注意 2023/4 時点で pug のモジュールが古いままになっているようです。html そのままにしたい場合は下記にします。
$ express app_name --no-view
生成のコマンド名は express
だけど、使用するには express-generator
をインストールする必要がある。
$ npm install express-generator -g
または
$ yarn add global express-generator
実行
$ yarn start
ポート番号を指定した場合は、PORT=8000 yarn start
とする。ポートを 8000 とした場合、http://localhost:8000
でアクセスできる。プログラムが正常に起動していれば、下記のページが表示される。
余談:yarn start と package.json の関係
コマンドラインから yarn start
とすると、yarn
が package.json
の scripts
の中を見て、start
に書かれているコマンドを実行する。ためしに package.json
の中の start
を hogehoge
にかきかて、yarn start
してみると…
...
"scripts": {
"hogehoge": "node ./bin/www"
},
...
$ yarn start
error Command "start" not found.
このように、エラーになる。そのかわり、yarn hogehoge
で実行できる。package.json
の hogehoge
を start
に戻すと、再び yarn start
で実行できるようになる。
余談: yarn run
yarn run
とすると、yarn
が scripts
の中を見て、どの項目を実行したいのかと聞いてくる。
$ yarn run
yarn run v1.22.4
info Commands available from binary scripts: acorn, babylon, cleancss, mime, uglifyjs
info Project commands
- hogehoge
node ./bin/www
question Which command would you like to run?:
ここで start
と入力すると、node bin/www
が実行される。
express-generator
ではデフォルトで実行コマンド名を start
としているので、yarn start
として実行しているにすぎずstart
というコマンドが yarn
にあるわけではない(らしい)。そのため、先のように自分でコマンド名を別のものに書き換えたり、追加することもできる。
たとえば、デバッグ時は 8000 番ポートにして、本番は 80 番にしたいみたいなときに、下記のように書いておく。
"scripts": {
"start": "node ./bin/www",
"test": "PORT=8000 node ./bin/www"
},
これで yarn test
または yarn run test
とすると、8000 番ポートで実行され、yarn start
または yarn run start
だと 80 番ポートで実行できるようになる。
add
のような yarn
に元からあるコマンドを "start"
と同列に追加したい場合は、yarn add
ではダメで yarn run add
とする必要がある(そんなことするのかは謎)。
余談: node bin/www と yarn start の違い
コマンドラインから node www/bin
としてもアプリの実行はできる。この場合、package.json
の start
に書かれている内容は無視される。
前述のように、引数などを package.json
の start
に書いている場合は、node bin/www
としたときと yarn start
で挙動が異なることになる。
生成ファイル
express generator 4.16.1
で --view=pug
オプション付きで生成した場合、下記のファイル構造になる。
project_dir
├── app.js # アプリのメインファイル
├── bin
│ └── www # yarn start 時に node bin/www として実行されるファイル
├── package.json # ライブラリ等の依存関係やバージョン情報を格納したファイル
├── public # static なファイルを置くフォルダ
│ ├── images # http://localhost:8000/images
│ ├── javascripts # http://localhost:8000/javascripts
│ └── stylesheets # http://localhost:8000/stylesheets
│ └── style.css # http://localhost:8000/stylesheets/style.css
├── routes # router (ミドルウェア) 置き場
│ ├── index.js # http://localhost:8000/ (トップページ)
│ └── users.js # http://localhost:8000/users
└── views # テンプレートファイル置き場
├── error.pug # エラー時のテンプレート
├── index.pug # index.js 用のテンプレート
└── layout.pug # index.pug や error.pug に読みこまれるテンプレート
static ファイル
下記の内容で index.html
を作り、public
直下に置く。
<!DOCTYPE html>
<head></head>
<body>
<h1>Hello World</h1>
</body>
</html>
この状態で PORT=8000 yarn start
すると、http://localhost:8000
で表示される内容は下記のようになる。
public
以下にファイルを置くと、ブラクザから直接そのファイルにアクセスできるようになる。ただし、アクセスするパスは /public
ではなく /
になる。
たとえば、上記のように public/index.html
を作成した場合、http://localhost:8000/public/index.html ではなく、http://localhost:8000/index.html としてアクセスできる。
http://localhost:8000 のように、ファイル名を指定しない場合は、デフォルトで index.html
が読みこまれる (ファイルが存在していれば)。
このような動作をするのは、app.js
に下記のコードがあるため。
app.use(express.static(path.join(__dirname, 'public')));
この記述で、public
以下のディレクトリにあるファイルは、http://localhost:8000/
以下にあるものとして、そのままクライアントに返される。
単純な web サーバとして使うだけなら、public 以下にファイルを置くだけで十分に機能する。
余談:static ディレクトリの変更、追加
ディレクトリ名を public
以外にすることもできるし、複数のディレクトリを指定することもできる。
app.use(express.static(path.join(__dirname, 'public')));
app.use(express.static(path.join(__dirname, 'video')));
app.use(express.static(path.join(__dirname, 'img/hoge')));
この場合、いずれのディレクトリにあるフィルも、URL としては /
にあるものとしてアクセスされる。dat/img/hoge.txt
もhttp://localhost:8000/hoge.txt
でアクセスできる。
static に指定されている複数のディレクトリに同名のファイルがある場合は、app.js
内で、より先にある行の記述が優先される。たとえば、上のコードの状態で実行したとして、
public
├── images
└── index.html
└── style.css
video
├── index.html
└── hoge.txt
img
└── hoge
└── hoge.txt
このように、public/index.html
と video/index.html
が存在している場合に、http://localhost:8000
にアクセスした場合、public/index.html
がブラウザに返される。同様にして、http://localhost:8000/hoge.txt
へアクセスした場合は video/hoge.txt
が返される。
余談:同じパスを示す app.use() は先にあるものが優先される
先に書いた行が優先というルールは、他の app.use()
の記述にも適用される。
var indexRouter = require('./routes/index');
app.use(express.static(path.join(__dirname, 'public')));
app.use('/', indexRouter);
app.use('/', indexRouter)
という記述は、http://localhost:8000
にアクセスがあった時に、./routes/index.js
の中を実行することを意味している。public/index.html
が存在しなければ、app.use('/', indexRouter)
が実行されて、下記のページが表示される。
しかし、public/index.html
が存在していると、先にある static
の記述のほうが優先されて、public/index.html
の内容が表示される。
app.use()
の順序を逆にすると、優先順位も逆になる。
var indexRouter = require('./routes/index');
app.use('/', indexRouter);
app.use(express.static(path.join(__dirname, 'public')));
この順序の場合、'public/index.html' が存在していても、app.use('/', indexRouter)
の記述が優先される。
public/index.html
を置くと、このような混乱が起こるので、分かっていてやるのでなければ避けたほうが良いと思う。もし分かってやってるなら、むしろ app.use('/', ... )
の記述を消したほうが良い、また public
ディレクトリ以下に、他の app.use()
で使っているパス名と同じパスがあると、同様の混乱がおこりやすいため、それも避けたほうが良い(と思う)。
余談:path.join(__dirname, '') の意味
下記の index.html
を public/index.html
に置いておき、
<!DOCTYPE html>
<head></head>
<body>
<h1>Hello World</h1>
</body>
</html>
app.js
内の、
app.use(express.static(path.join(__dirname, 'public')));
app.use('/', indexRouter);
この記述を、
app.use(express.static('public'));
app.use('/', indexRouter);
このように変更し、アプリケーションのフォルダ内で yarn start
してみる。ブラウザで http://localhost:8000
にアクセスすると、下記のページが表示される、はず。
しかし、bin ディレクトリに入ってから、下記のようにしてアプリケーションを実行し http://localhost:8000
をブラウザで開くと…
$ cd bin
$ node www
このようになる、これは public/index.html
が無視されて app.use('/', indexRouter);
が実行されていることを意味する。
なぜこうなるかというと、node コマンドは実行したフォルダをカレントディレクトリとして認識するからのようだ。つまり、bin
ディレクトリ内で node を実行すると、public
ディレクトリを探す起点(ルート)が bin
ディレクトリになってしまい、bin/public
を探しにいってしまうから、ということ。
bin
ディレクトリ内に public
ディレクトリが存在しない場合、app.use(express.static('public'));
の記述は無視されて(マッチせずに) app.use('/', indexRouter);
のほうが実行される。これは bin
が実行時のカレントディレクトリになり、express.static('public')
という記述は、bin/public
ディレクトリを指すようになるためである。
試しに、次のように書きかえると、bin
ディレクトリ内で node www
を実行しても、http://localhost:8000
へアクセスしたときのブラウザには "Hello World" が表示されるようになる。
app.use(express.static('../public'));
ここで、下記のように path.join()
を使った記述に戻すと、bin
ディレクトリ内で node www
とした場合でも "Hello World" と表示されるようになる。
app.use(express.static(path.join(__dirname, 'public')));
これは、app.js
(path.join()
が記述されているファイル)があるディレクトリが __dirname
に自動的に入れられるため。つまり、path.join(__dirname, name)
とすることで、__dirname
と name
が連結され、結果的に app.js
のあるディレクトリを起点(ルート)として public
ディレクトリを探してくれる。
アプリのフォルダ以外からアプリを起動することは、そうそう無いと思うのだけど、たまーにそういうことをしてハマることがある。特段の事情がない限り、実在するファイルやフォルダのパスを指定するときは path.join(__dirname, name)
としたほうが良い。
一方で、__dirname を app.use
の第一引数や、外部参照するURLのパスの指定に使うとだいたいバグになる(分かっていたらやらないはず…)。
後述するように、routes
ディレクトリ内にあるファイルで path.join(__dirname, name)
を使用したときは、routes
ディレクトリがカレントディレクトリ (__dirname
) になる。app.js
のあるディレクトリではない。これはよく間違う。
また、app.use(path, ... )
の一つ目の引数で指定する path
は、実在するファイルのパスを指定する引数ではない。この引数で path.join()
を使うのは(分かってやっているのではない限り)誤りである。
余談:path でファイル名操作
path
は join()
以外にも、ファイル名を操作する便利な機能がいろいろある。詳しくは以下を参照のこと。