メインコンテンツまでスキップ

Express とテンプレートエンジン

Express パッケージを用いて HTTP サーバーを構築する

Express パッケージ を用いると、Node.js 標準の http モジュールよりも簡単に Web サーバーを構築できます。

まずは express パッケージを npm でインストールします。

npm install express

続いて、次のような main.js を作成しましょう。

main.js
const express = require("express");
const app = express();

app.get("/", (request, response) => {
response.send("Hello Express");
});

app.listen(3000);

main.js を起動することにより、 http://localhost:3000/Hello Express が表示されます。

前頁http 標準モジュールを用いた例とほとんど同じことを行うプログラムになっていますが、express を使う場合は少々異なる部分があります。まず、require("express") の戻り値は関数となっており、この関数を呼び出すことにより、express.Application クラスのインスタンスが作成されます。

express.Application#get メソッドは、http 標準モジュールにおける request イベントハンドラの登録に相当する操作を行うためのメソッドです。イベントハンドラの引数に requestresponse が存在する点では一致していますが、Express では express.Response#send メソッドが利用できます。これは、http.ServerResponse#write メソッドと、http.ServerResponse#end メソッドを順番に呼ぶ操作に対応します。

静的ホスティング

次の例では、//script.js/sub//sub/script.js へのリクエストについて、それぞれファイルから読み込んでレスポンスを送信しています。

const express = require("express");
const fs = require("fs");
const app = express();

app.get("/", (request, response) => {
response.send(fs.readFileSync("static/index.html", "utf-8"));
});
app.get("/script.js", (request, response) => {
response.send(fs.readFileSync("static/script.js", "utf-8"));
});
app.get("/sub/", (request, response) => {
response.send(fs.readFileSync("static/sub/index.html", "utf-8"));
});
app.get("/sub/script.js", (request, response) => {
response.send(fs.readFileSync("static/sub/script.js", "utf-8"));
});

app.listen(3000);

express.static 関数を用いると、このような「リクエストを受け取ったら、そのパスに応じて適切なファイルを読み込んでレスポンスとして返す」という一連の動作を簡単に記述できます。

const express = require("express");

const app = express();
app.use(express.static("static"));
app.listen(3000);

これにより、リクエストのパスをもとに、static フォルダ内の適切なファイルが自動的に配信されます。

index.html の省略

express.static を用いる場合、index.html は省略可能になります。つまり、/ へのリクエストで static/index.html が、/sub へのリクエストで static/sub/index.html にアクセスできるようになります。これは、Express や JavaScript に限ったことではなく、多くの Web サーバーの実装において、こういったルールが成り立ちます。

EJS テンプレートエンジン

前項のプログラムを書き換えて、複雑な HTML を出力できるようにしてみましょう。

const express = require("express");
const app = express();

const names = ["田中", "鈴木", "佐藤"];
app.get("/", (request, response) => {
response.send(`
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8" />
<title>Title</title>
</head>
<body>
<ul>
${names.map((name) => `<li>${name}</li>`).join("")}
</ul>
</body>
</html>
`);
});

app.listen(3000);
Array#join

Array#join メソッドは、配列を指定した区切り文字で結合した文字列を返すメソッドです。

console.log(["Apple", "Banana", "Orange"].join("/")); // Apple/Banana/Orange

なかなか大変なことになっています。これから HTML がもっと長くなったり、さらに複雑なプログラムが必要になってきたらこのまま続けていくのは難しそうです。

EJS をはじめとしたテンプレートエンジンは、プログラミング言語から HTML などを作成する作業を簡単にしてくれます。ejs パッケージを npm でインストールしてください。先ほどのプログラムを、EJS を用いて書き換えると、次のようになります。

main.js
const fs = require("fs");
const express = require("express");
const ejs = require("ejs");
const app = express();

const names = ["田中", "鈴木", "佐藤"];
app.get("/", (request, response) => {
const template = fs.readFileSync("template.ejs", "utf-8");
const html = ejs.render(template, {
listItems: names,
});
response.send(html);
});

app.listen(3000);
template.ejs
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8" />
<title>Title</title>
</head>
<body>
<ul>
<% for (const listItem of listItems) { %>
<li><%= listItem %></li>
<% } %>
</ul>
</body>
</html>

イベントハンドラの中で、プログラムではまず fs.readFileSync 関数を用いて template.ejs ファイルの内容を読み込んでいます。その次の行の ejs.render 関数 がポイントです。この関数は、第 1 引数にテンプレートを文字列として受け取り、諸々の変換を行った後の文字列を返します。第 2 引数には、変換の際に埋め込みたいデータをオブジェクトの形式で指定します。

このオブジェクトのキーと同じ名前の変数が、テンプレート内で利用できます。上の例の template.ejs における listItems は、main.js で指定した { listItems: names } により ["田中", "鈴木", "佐藤"] になります。

テンプレート内の <% から %> で囲まれた部分は、JavaScript のプログラムとして実行されます。また、<%= から %> で囲まれた部分は JavaScript の式として評価され、最終的な結果に埋め込まれます。

課題

  • Express を用いて、あなたは n 人目のお客様です。 とレスポンスする Web サーバーを作成してください。n はアクセスされるたびに 1 ずつ増えるようにしてください。
  • 上記プログラムに EJS を追加してみてください。
  • (重要) アクセスされた時刻をウェブサーバー側で求めて表示するウェブサーバーと、ブラウザに求めさせるウェブサーバーをそれぞれ作成してください。
    • この 2 つの違いは何でしょうか。どういった場合にどちらの手法を使うのが適切でしょうか。

解答例 1:

解答例 2:

解答例 3: