ExpressのsendFile()とrender()の違い

家庭教師でExpressを用いてWebアプリ制作の指導をしているのだが、renderとredirectが出てきて、それらの違いが何かと聞かれると説明するのが難しいし、実際その二つで異なる挙動を示すので、今回まとめてみることにした。

まず以下のようなコードを想定する。説明用に簡単にして余計なコードは消したので、これだけだと動かないと思われる。

const express = require('express');
const session = require('express-session');
const app = express();

app.get('/login', (req, res) => {
  //下のどちらかを実行
  // res.render('index');
  // res.sendFile('index.html');
});

app.use((req, res, next) => {
  if (req.session.username) {
    next();
  } else {
    res.redirect('/login');
  }
});

まず、app.useとはミドルウェア関数を定義するもので、このミドルウェア関数は引数にパスを指定しないと、アプリケーションにアクセスがあるたびにロードして実行される。例えば、以下のように書くと、/user/:idにアクセスされた時にだけ、実行されるミドルウェア関数である。next();という関数は、次のミドルウェア関数(定義された順に実行される)に実行権限を渡す関数で、基本的にはミドルウェア関数では最後に実行する必要がある。例外として、これ以降のミドルウェア関数を無視する場合、next('route')と書く。

app.use('/user/:id', (req, res, next) => {
  console.log('Request Type:', req.method);
  console.log('ID:', req.params.id);
  next();
})

またミドルウェア関数は定義された順番に実行されるので、コード内のどこに配置するかは重要となってくる。

話を戻すと、最初の例のapp.useで行っている処理としては、セッションにusernameの情報が入っていれば、next()関数を実行し、そうでないとlocalhost:3000/loginに対してredirectするという処理だ。つまりログインする前はサービスのページにはアクセスできないとするサービスを作る上では当たり前の処理だ。

ここで問題なのが、redirectした後だ。ここでredirectすると上のapp.getでGETメソッドが走る。静的なindex.htmlファイルを表示したいのだが、選択肢としてコメントアウトした二つが考えられる。

まず私自身res.sendFile('index.html');を書いた。これで実行できると思ったのだが、リダイレクトが繰り返されるというエラーが出た。res.send()を使って引数に直接htmlファイルを書くと正しく実行することができたのだが、sendFileにするとうまく実行できない。最初はなぜかよくわからなかったが、res.sendFileが実行されるたびにミドルウェア関数が実行されて、再びリダイレクトされるということが繰り返される原因ではないかと思った。

今度は、res.render('index');と書いて実行してみた。htmlファイルを想定して正しく実行させるために以下のコードを必要とした。

// view engine setup as html
// https://stackoverflow.com/questions/17911228/how-do-i-use-html-as-the-view-engine-in-express
app.set('views', path.join(__dirname, 'view')); // ここで静的ファイルの場所を指定している。
app.engine('html', require('ejs').renderFile);
app.set('view engine', 'html');

// 静的ファイルが格納されているディレクトリを指定
app.use(express.static('view'));

これによって正しく実行された。どうやらミドルウェア関数がこの場合は実行されないっぽい。renderで行われていることとしては、テンプレートエンジンを使用してファイルをパースして、htmlファイルを出力しているようだ。node.js - Express.js sendfile() vs. render() - Stack Overflow によるとhtmlファイルを扱っている場合は、出力結果が同じになるようだが、sendFileではミドルウェア関数が実行され、renderではミドルウェア関数が実行されないという違いがあるように思われる。

しかし重要なことに気づいた。それはrender()を使用すると、画面の遷移の際にURLを変えることができないという点だ。これはだいぶ困るので、やはりsendFile()を使った無限にリダイレクトされない方法があるのだろうか。なんとなく定義する順番な気がするが真相はわからない。