Lesson 3

画面を作ってみる

Lesson 3 Chapter 1
EJSのインストール

前回のLesson2では、Node.jsを使ってWebページに文字を表示させました。

今はまだシンプルな画面しか作成できていませんが、応用すればメールやショッピングサイト、SNSといった複雑なWebアプリケーションも作成可能です。

ここからのLessonでは、テンプレートエンジン「EJS」とフレームワーク「Express」を使って、文字と画像が表示される簡単なWebアプリケーションを作成しましょう。

EJSのインストール

まずはEJSをインストールしましょう。

Lesson1-4「EJSとは」で述べたように、EJSはNode.jsのパッケージとして提供されています。以下の通り、npmを使ってインストールしてください。

ターミナル
npm install ejs

npm initを行ったばかりの状態では、「package.json」には何のパッケージ情報も記述されていません。

今回EJSのパッケージをインストールしたことで、「package.json」のdependencies以下にejsの表記が追加されているはずです。

また、同時に作業ディレクトリ直下に「node_module」ディレクトリと「package-lock.json」ファイルが作成されています。

このうち「node_module」ディレクトリには実際にインストールしたパッケージ(依存関係を含む)が、「package-lock.json」にはその具体的なバージョン情報が保存されています。

実行後のディレクトリ構成
practice
 ├─ node_modules/ //新規作成される
 ├─ hello.js
 ├─ main.js
 ├─ package-lock.json //新規作成される
 └─ package.json

上記のフォルダ構成になっていれば完了です。

環境構築

上記の構成はLesson2から続いています。新しく作業を始めたい方は任意のディレクトリ直下で「npm init」を入力し、package.jsonファイルを作成してください。

Lesson 3 Chapter 2
Expressのインストール

Expressのインストール

続いて、フレームワークである「Express」をインストールします。ExpressもNode.jsのパッケージとして提供されており、npm installでインストール可能です。

ターミナル
npm install express

EJSと合わせて2種類のパッケージをインストールしました。「package.json」を確認し、dependencies以下に両パッケージの名前が記述されていれば完了です。

{
  "name": "new_project",
  "version": "1.0.0",
  "description": "New Project",
  "main": "main.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "Tarou node",
  "license": "MIT",
  "dependencies": {
    "ejs": "^3.1.8", //バージョンが異なる場合があります。
    "express": "^4.18.1" //バージョンが異なる場合があります。
  }
}

EJSとExpressのバージョンについて

本教材が依拠しているのは上記のバージョンです。皆さんの実行環境によっては、教材と同じ記法が使えない場合もあります。その際はお手数ですが、バージョンを下げる、公式ドキュメントを参考に読み進めるなどの対応を講じてください。

サンプルページの作成

ここでは、異なる大きさの文字列を表示するWebページを作成します。 Node.jsで特定の処理を実行し、その結果を返すというアプリケーションの基本動作を学びましょう。

まずは、ディレクトリ直下に「index.ejs」「man.js」のファイルを作成します。以下と同じ構成であることを確認しましょう。

フォルダ構成
practice
 ├─ node_modules/
 ├─ hello.js
 ├─ index.ejs
 ├─ main.js
 ├─ package-lock.json
 └─ package.json

「index.ejs」を以下の通り編集してください。ここで記述した内容がWebページに表示されます。

index.ejs
<!DOCTYPE html>
  <html lang="ja">
                      
  <head>
    <meta http-equiv="content-type"
      content="text/html; charset=UTF-8">
    <title>Index</title>
  </head>
                      
  <body>
    <header>
      <h1>Hello World!</h1>
    </header>
    <div role="main">
      <p>これからEJSを使ってこのWebページを作成していきます。</p>
    </div>
  </body>
                      
</html>

見ての通り、上記自体はHTMLのコードに過ぎません。したがって、styleタグを使えば文字の太さや色を編集することも可能です。

次に、「main.js」を以下のように編集してください。

main.js
const http = require('http');
const fs = require('fs');
const ejs = require('ejs');
                      
const index_page = fs.readFileSync('./index.ejs', 'utf8');
                      
var server = http.createServer(getFromClient);
                      
server.listen(3000);
console.log('Standby.');
                      
function getFromClient(request, response) {
  var content = ejs.render(index_page);
  response.writeHead(200, {'Content-Type': 'text/html'});
  response.write(content);
  response.end();
}

コードの説明は後述します。編集が完了したらターミナルで以下のコマンドを入力し、「main.js」を実行しましょう。

ターミナル
node main.js

実行後、ターミナル上に「Standby.」の文字が表示されていれば準備完了です。 ブラウザでlocalhost:3000を開いてください。

ターミナル
> node main.js
Standby.

ejs-sample-page.png 作成したWebページ

上記のようなページが表示されていれば成功です。それでは、サンプルコードの具体的な記述内容を見ていきましょう。

サンプルコードの内容

ターミナル
const http = require('http');
const fs = require('fs');
const ejs = require('ejs');

require()については、Lesson2-5「Hello Worldをブラウザに表示する」で説明しました。 ここでは「http」に加えて、Node.js公式のモジュールである「fs」、および「EJS」を読み込んでいます。

fsモジュールとは

fsモジュールはファイルの新規作成や読み込み、書き込みなどファイルの操作に関する機能を備えたモジュールです。

main.js
const index_page = fs.readFileSync('./index.ejs', 'utf8');

上記では、早速fsモジュールの機能を利用しています。

readFileSyncメソッドにより、引数に指定したファイルを文字列として読み込んでいます。第2引数にutf8とあるのは、使用される文字コードがUTF-8であるためです。

main.js
var server = http.createServer(getFromClient);

Lesson2-5に登場したcreateServerメソッドと同じです。 引数に指定した関数getFromClientについては後述します。

main.js
server.listen(3000);

こちらもLesson2-5で使用したものと同一です。サーバーのポート番号として3000を指定しています。

main.js
console.log('Standby.');

サーバー立ち上げやlistenメソッドなどが完了した際にメッセージを表示します。

main.js
function getFromClient(request, response) {
  var content = ejs.render(index_page);
  response.writeHead(200, {'Content-Type': 'text/html'});
  response.write(content);
  response.end();
}

レンダリング処理

最後に、説明を保留にしていたgetFromClient関数の解説です。

この関数の内部では、EJSのrenderメソッドが使用されています。renderメソッドはEJSのテンプレートファイル(ここでは「index.ejs」)をHTMLに変換し、HTMLコードを生成しています。

レンダリングの必要性

「index.ejs」の中身はHTMLファイルそのものであるため、わざわざレンダリング処理を行う必要はありません。 ただし、EJSの使い方を分かりやすく学ぶため、今回はあえて記述しています。

,

response.writeHeadresponse.writeresponse.endはHTMLファイルの返却をブラウザに伝える処理です。。

ここまでmain.jsの解説を行いました。大まかな流れを振り返りましょう。

  1. EJSなどの必要なモジュールを読み込む。
  2. 表示したいテンプレートファイル(今回は「index.ejs」)をreadFilereadFileSyncメソッドで読み込む。
  3. renderメソッドで表示させたいHTMLコードを生成する。
  4. 生成されたHTMLを出力する。
上記を基本的な流れとして、EJSによるレンダリングが行われます。

Lesson 3 Chapter 3
サンプルページを実装

ここまで、EJSとExpressのインストール方法を解説しました。

環境構築を終えたところで、両パッケージを使ったサンプルページの実装に移りましょう。

プロジェクトの作成

実装の下準備として、npmプロジェクトの初期化を行います。

まずはプロジェクト用に新規のディレクトリを作成してください。以下はコマンドライン上で操作する場合の例です。

~/Documents/workspace
$ mkdir sample
$ cd sample-project

以降、この「sample-project」をディレクトリ名として解説していきます。

npmプロジェクトの初期化

続いて、プロジェクトの初期化を行いましょう。

プロジェクトの初期化には、npm initコマンドで「package.json」を作成する必要があります。覚えていない方はLesson2-4「パッケージのインストール」を復習してください。

作成したディレクトリ(例では「sample-project」)配下で以下を実行します。

~/Documents/workspace/sample-project
$ npm init

npmのオプション

npm init -yとオプションを入れることで、質問をスキップして自動でpackage.jsonを作成してくれます。

ライブラリのインストール

それではExpressとEJSのインストールを行っていきましょう。

先ほど作ったディレクトリ配下で下記のコマンドを実行します。

~/Documents/workspace/sample-project
$ npm install express ejs

二つのパッケージがインストールされているか、「package.json」を開いて確認しましょう。正しくインストールされていれば、dependencies以下にパッケージ名とバージョンが記載されています。

package.json
{
  "name": "sample",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "ejs": "^3.1.8",
    "express": "^4.18.2"
  }
}

appディレクトリの作成

機能ごとにコードを整理するため、「app」という名前のディレクトリをプロジェクト直下に作成しましょう。

~/Documents/workspace/sample-project
$ mkdir app

作成後、以下のディレクトリ構成であることを確認してください。

dir_dif-ch3-1.png ~/Documents/workspace/sample-project

サンプルページの実装

それでは、サンプルページの実装を行なっていきましょう。

「app」ディレクトリ配下に2つのファイルと1つのディレクトリを作ってください。以下はコマンドラインでの操作例です。

~/Documents/workspace/sample-project/app
touch index.js
mkdir views
cd views
touch index.ejs

正しく作成できていれば、「app」配下のディレクトリ構成は以下のようになっています。

dir_dif-ch3-2.png ~/Documents/workspace/sample-project

expressの初期設定

Expressの初期設定として、「index.js」を編集します。

index.js
const express = require('express')
const app = express()

app.get('/', (req, res, next) => {
  res.send('hello express')
})

app.listen(3000, () => {
  console.log('Server started on port 3000')
})

上記では、requireを使ってExpressを読み込んでいます。requireは主にライブラリからモジュールを読み込むための構文です。

Expressを実行すると、定数appにExpressの情報を格納したオブジェクトが格納されます。

app.getでは、第1引数で指定したパスにリクエストが届くと、第2引数の関数を実行するように設定しています。

app.listenはローカルサーバーを立てています。引数で指定した「ポート番号:3000」にアクセスするとレスポンスを返す仕組みです。

ローカルサーバーとは

自身のPC内に立てられた、外部のネットワークに接続されていないサーバーのこと。

ローカルサーバーにアクセスしてみる

それではファイルを実行しましょう。

~/Documents/workspace/sample-project/app
$ node app/index.js

ブラウザで「localhost:3000」にアクセスしてください。

hello_express-ch3-1.png

上記の画像のように表示がされたら成功です。

続いて、ExpressをEJSと繋げるための設定を行います。「index.js」を以下のように編集してください。

index.js
const express = require('express')
const path = require('path')
const app = express()

app.set('views', path.join(__dirname, 'views'))

app.set('view engine', 'ejs')

app.get('/', (req, res, next) => {
  res.render('index.ejs')
})

app.listen(3000, () => {
  console.log('Server started on port 3000')
})

EJSの初期設定

続いて、EJSの初期設定として「index.ejs」を編集します。

index.ejs
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>sample-project</title>
  </head>
  <body>
    <h1>Hello Ejs</h1>
  </body>
</html>

再びブラウザから「localhost:3000」にアクセスしてください。画像のように表示されたら成功です。

正しく表示されない場合、一旦サーバーを停止させ、「index.js」を実行し直してみてください。

hello_ejs-ch3-1.png.png"

まとめ

以上でサンプルページの実装は終了です。

最初からコードの細部を理解する必要はありません。まずはディレクトリ構成や各ファイルの役割など、アプリケーションの全体像を掴むようにしてください。

次のXhapterでは、変数を使って動的なデータを画面に描画します。少しずつステップアップできるように頑張りましょう。

Lesson 3 Chapter 4
変数を扱う ()

Chapter3では、Node.jsのフレームワーク「Express」とテンプレートエンジン「EJS」を組み合わせ、静的なWebページを実装しました。

とはいえ、まだHTMLを直接Webページにレンダリングしただけであり、EJSの機能は活かし切れていません。EJSの強みは、サーバー側のレスポンスに応じて、レンダリングする内容を自在に変更できる点にあります。

そこで今回は、変数を用いた動的なWebサイトの表示に挑戦してみましょう。

EJSで変数を使ってみる

EJSで変数を扱う方法については、Lesson1-4「EJSとは」で簡単に紹介しました。忘れてしまっていても問題ありません。実装しながら復習しましょう。

変数定義

早速ファイルを編集していきます。「index.ejs」を以下のように編集してください。

index.ejs
...省略
  <body>
    
    <h1></h1>
  </body>
...省略

編集後、コマンドラインから「index.js」を起動してください。

/sample-project
$ node app/index.js

ブラウザからlocalhost:3000にアクセスすると、以下のように表示されているはずです。

hello_ejs-ch4-1.png

あらためて「index.ejs」の内容を確認してみましょう。

EJSでは、タグを用いることでJavaScriptの式を直接記述できます。また、JavaScriptの変数を展開するときはを用います。

今回の例では、変数titleに代入された文字列がh1タグの中で展開、Webページに出力されました。

以上がEJSにおける変数の基本的な使い方です。

レスポンスの受け取り

先ほどの例は、テンプレートの中で変数の値を取得するケースです。一方、サーバーからのレスポンスとして変数を受け取ることもできます。

実践してみましょう。「index.js」と「index.ejs」ファイルを以下の通り編集してください。

index.js
...省略
app.get('/', (req, res, next) => {
  res.render('index.ejs', { title: 'Hello Ejs サーバーからのレスポンス' })
})
...省略
index.ejs
...省略
  <body>
    <h1></h1>
  </body>
...省略

編集後、Node.jsでサーバーを立ち上げます。先ほどのサーバーが起動したままの場合は、ctrl + cでサーバーを止めてから再起動してください。

/sample-project
$ node app/index.js

以下と同じ画面が表示されたら成功です。

hello_ejs_response-ch4-1.png

renderメソッドの使い方については、次のChapter5で詳しく解説します。ここでは、サーバーから変数を受け取れるという点を気に留めておきましょう。

確認:EJSでの変数の扱い方

EJSではなどの特殊なタグを用いて変数やJavaScript式を扱えます。 を使うことでサーバーからのレスポンスを受け取ることも可能です。

ループで配列の要素を表示する

応用編として、今度は複数のデータを展開してみましょう。

たとえば、liタグを用いてリスト形式で出力するなど、複数の要素を一括して扱いたい場合があります。そんな時はJavaScriptの「配列」を使うと便利です。

配列であれば要素全体をひとつの変数で管理できます。先ほどの例と同じく、個々の要素はタグで表示可能です。

早速ファイルを編集していきましょう。

index.js
...省略
app.get('/', (req, res, next) => {
  res.render('index.ejs', { title: '' })
})
...省略
index.ejs
...省略
  <body>
    <&gt
    
    
      <li></li>
    
  </body>
...省略

今までと同様に、コマンドラインからNode.jsでサーバーを起動します。

/sample-project
$ node app/index.js

画像のように表示されたら成功です。

list-ch4-1.png

上記の例では、文字列の配列としてtodosが使われています。

その直後に各要素を出力していますが、注目すべきはforEachメソッドです。

forEachは配列の要素をひとつずつ受け取り、引数に指定した関数の処理を実行します。今回は要素をliタグに展開する関数を渡したため、画像のように一覧で表示されました。

条件分岐する

EJSではifを用いた条件分岐も可能です。サーバーから渡された値を参照し、場合に応じて処理を振り分けます。

早速ファイルを編集していきましょう。

index.js
...省略
app.get('/', (req, res, next) => {
  res.render('index.ejs', { need: 'おにぎり' })
})
...省略
index.ejs
...省略
<body>
    <h1>Todo</h1>
    <div>
      
      
      <p>>/p>
      
      <p></p>
      
      <p></p>
      
    </div>
  </body>
...省略

編集後、コマンドラインからNode.jsでサーバーを起動します。

/sample-project
$ node app/index.js

画像の通り表示されたら成功です。値や条件を変更し、何度か挙動を確認してみましょう。

if-ch4-1.png

この他にもEJSには様々な記述方法がありますが、どれも直感的で分かりやすいものです。公式サイトのドキュメントに目を通し、ある程度慣れておくといいでしょう。

まとめ

今回はEJSで変数を扱う方法を学びました。EJSを導入すると、このようにJavaScriptの延長線上でHTMLを処理できるようになります。

少しずつ、Webアプリケーション開発に必要なスキルが身についてきました。次のChapterでは、Expressのrenderメソッドについて詳しく見ていきます。

Lesson 3 Chapter 5
変数を扱う(render)

前回のChapterでは、EJSによる変数の扱いについて学習しました。その中でも少し触れましたが、変数はテンプレート内で宣言・代入できるほか、サーバー側のレスポンスとして受け取ることも可能です。

今回は、Expreesのrenderメソッドを使用し、クライアントであるEJSに変数を渡す方法について学習しましょう。

EJSに動的な値を渡す

以下が元となるファイル内容です。変数todoがテンプレートファイル内で指定されているため、このままでは静的なページしか表示できません。

index.ejs
...省略
<body>
  <h1>Todo</h1>
    
    <div>
      <p>今日やること:</p>
      <p>期限:</p>
    </div>
</body>
...省略

そこでExpressのrenderメソッドを修正し、サーバーから変数の値を受け取るように変更しましょう。

renderの使用

それでは具体的に書き換えていきます。テンプレートファイル「index.ejs」内で行っていた変数todoは削除し、受け取った変数を出力する処理のみ記述します。

index.ejs
...省略
<body>
  <h1>Todo</h1>
    <div>
      <p>今日やること:</p>
      <p>期限:</p>
    </div>
</body>
...省略

一方、サーバー側である「index.js」は以下の通り書き換えます。

index.js
const express = require('express')
// 追加
const ejs = require('ejs')
const path = require('path')
const app = express()

app.set('views', path.join(__dirname, 'views'))

app.set('view engine', 'ejs')

// ----- 追加・変更 -----
const todo = { title: 'おにぎりを買う', timeLimit: '今日' }
app.get('/', (req, res, next) => {
  res.render('index.ejs', { todo: todo })
})
// ---------------------

app.listen(3000, () => {
  console.log('Server started on port 3000')
})

成功すれば、画像の通り表示されるはずです。

Todoアプリ

「index.js」の内容を振り返りましょう。renderメソッドは第1引数に読み込むテンプレートを、第2引数に渡したいデータを指定できます。

今回の例では、第2引数に渡す値としてtitleの文字列を指定しました。後は先ほど同様に、EJSのタグによって変数が展開される仕組みです。

renderメソッドを介してテンプレートファイルの外からデータを渡せるようになりました。これで動的なWebページが生成できるようになります。

まとめ

Expressのrenderメソッドを用いると、レスポンスとして変数の値を渡すことが可能です。

テンプレートエンジンで扱う変数をサーバー側で生成することで、動的なWebページを構築できます。

基本的にはテンプレート内で変数を宣言せず、このようにサーバー側から受け取るようにしましょう。サーバー側でデータを一元管理することで、保守性の高い設計が実現できます。

Lesson 3 Chapter 6
ルーティングの実装

ここまでのChapterでは、Webページの描画処理(レンダリング)を中心に解説してきました。

ここで実際のWebアプリケーションを考えてみましょう。ユーザーはひとつのURLだけにアクセスするわけではありません。

同じECサイトでも、商品を検索したい時と購入したい時とではリクエストが異なるはずです。サーバーはユーザーの要求に応じて、適切なレスポンスを返す必要があります。

そこで今回のChapterでは、Expressを用いたルーティングの基本を学びましょう。ルーティングの実装そのものは簡単ですが、アプリケーション設計の土台となる大切な箇所です。仕組みを十分に理解した上で進むようにしてください。

ルーティングとは

そもそもWebアプリケーションにおけるルーティングとは、クライアントのリクエストに応じて、サーバー側で処理を切り替えることを指します。

冒頭に挙げた例でいえば、商品の検索時は検索ページ用、購入時は購入ページ用のHTMLを返す必要があるでしょう。

また、クライアントの要求はWebページの表示に留まりません。商品の在庫を更新したい場合など、内部でデータベースとの接続を促すようなリクエストも送信されます。

こうした無数のリクエストを適切に振り分けることが、ルーティングの担う役割です。

routing_ch6-1.png Webアプリケーションにおけるルーティングのイメージ

サンプルアプリケーションの作成

さっそくルーティングの実践に移りましょう。今回は簡単なTodoアプリを想定し、一覧ページと詳細ページで表示を切り替えてみます。

プロジェクトの初期設定

まずは任意のディレクトリへ移動し、プロジェクトを新規作成します(ここでは「sample-app」とします)。

npm initによるプロジェクトの初期化と、npm installによるExpress・EJSのインストールを行ってください。

コマンドラインからは以下の操作で行えます。

コマンドライン(Windows / Mac / Linux)
$ mkdir sample-app
$ cd sample-app
$ npm init -y
$ npm install express ejs
                      

作成された「package.json」を開き、dependenciesに「Express」と「EJS」のバージョンが記載されていることを確認してください。

続いて「sample-app」直下に「app.js」を作成し、以下のコードを記述します。

app.js
const express = require('express')
const app = express()

app.get('/', (req, res) => {
  res.send('Hello world!!')
})

app.listen(3000, (req, res) => {
  console.log('Starting server on PORT: 3000')
})

それではサーバーを起動しましょう。「sample-app」配下で以下のコマンドを実行します。

ターミナル
$ node app.js

立ち上がったら「http://localhost:3000」にブラウザからアクセスしましょう。以下の画面が表示されたら成功です。

hello_world-ch6-1.png

ルーティングの実装

土台となるアプリケーションの作成ができました。それでは、本題であるルーティングの実装に移りましょう。

ルーティングといっても、最初のひとつはすでに実装してあります。

app.js
app.get('/', (req, res) => {
  res.send('Hello world!!')
})

Expressのgetメソッドは、対応するHTTPリクエスト(GET)を処理するものです。他にpostputdeleteといったメソッドがあり、それぞれHTTPリクエストの種別(POST・PUT・DELETE)に対応しています。

引数にも注目しましょう。getの第1引数はエンドポイントを表しています。エンドポイントとは、「https://ドメイン名/~」における「~」の部分です。

今回の場合、「http://localhost:3000/」にリクエストすると第2引数のコールバック関数が働き、「Hello world!!」をレスポンスとして返します。

Todoリストのルーティング

それでは、ここに別のルーティングを追加してみましょう。

今回は簡単なTodoリストを想定し、指定のURLにアクセスするとTodoの一覧を返すようなルーティングを実装します。Todoの一覧は「EJS」を使って表示させます。

プロジェクト直下に「views」ディレクトリを作成し、その配下に「index.ejs」ファイルを配置してください。

dir_dif-ch6-1.png 現在のディレクトリ構成

以下の通り「app.js」を編集し、EJSを参照できるようにします。

app.js
const express = require('express')
const path = require('path')
const app = express()

app.set('views', path.join(__dirname, 'views'))
app.set('view engine', 'ejs')

const todos = ['おにぎりを買う', 'お茶を買う', '散歩に行く']

app.get('/todos', (req, res) => {
  res.render('index.ejs', { todos: todos })
})

app.listen(3000, (req, res) => {
  console.log('Starting server on PORT: 3000')
})

上記の箇所では、新たに/todosパスのルーティングを作成しています。レスポンスとして「index.ejs」の内容を描画し、その際に配列todosを渡す流れです。

描画内容である「index.ejs」も編集しましょう。

index.ejs
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <h1>Todo一覧</h1>
    <ul>
       { %>
      <li></li>
      
    </ul>
  </body>
</html>

上記では、Expressで起動したサーバーから配列todosを受け取り、一覧で表示します。要素の表示には、Chapter4で紹介したforEachを用いています。

ここまでの記述が完了したら、ブラウザから「http://localhost:3000/todos」にアクセスしてみましょう。以下の通り表示されたら成功です。

todo_list-ch6-1.png

動的なルーティング

先ほど実装したのは、リクエストURLがつねに固定である場合のルーティングでした。一方で、リクエストURLが変化する場合も考えられます。

たとえば、Todoリストの詳細ページを考えてみましょう。詳細ページはTodoの内容(おにぎりを買う、散歩に行く…)によって異なります。Todoが増える度に/onigiri/sanpoなどとルーティングを増やしていたのでは切りがありません。

このような場合、エンドポイントを固定する方法は非現実的です。解決策として、動的なエンドポイントを実装してみましょう。

「app.js」を開き、app.listenメソッドの上に次の記述を追加します。

app.js
...省略
app.get('/todos/:id', (req, res) => {
  const id = req.params.id
  const todo = {
    id: id,
    title: todos[id - 1],
  }
  res.render('detail.ejs', { todo: todo })
})
...省略

動的なエンドポイントを受け取るには、コールバック関数の第1引数を使用します。この引数にはリクエスト情報が格納されており、エンドポイントの:の後に続く入力を受け取ることが可能です。。

今回の例では、配列todosのインデックスとしてidを取得し、対応する要素を返却するようにしています。

それでは詳細を受け取るテンプレートも作成していきましょう。「views」配下に「detail.ejs」ファイルを用意してください。

detail.ejs
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <h1>Todo詳細</h1>
    <p>id: </p>
    <p>タイトル: </p>
  </body>
</html>

ブラウザから「http://localhost:3000/todos/1」にアクセスしてみましょう。以下の通り表示されたら成功です。

todo_detail-ch6-1.png Todo詳細

まとめ

今回はルーティングについて解説しました。一見すると複雑なルーティングですが、仕組みが分かれば難しいことはありません。

フレームワークとしてExpressを用いると、最小限の記述で安全なルーティングが実現可能です。

今回は「GET」リクエストのみ扱いましたが、後のLessonでは「POST」など他のリクエストのルーティングも実装します。複雑な動的ルーティングに関しても、その際に学び直すことにしましょう。

Lesson 3 Chapter 7
fsモジュールで静的ファイルを提供する

今回は、fsモジュールを使った静的ファイルの扱い方を学びましょう。

ここでの「静的ファイル」とは、Webアプリケーションが処理するコンテンツのうち、リクエスト時に内容が生成・更新されないものを指します。具体的には、画像やCSS、JavaScriptなど、主にデザインやレイアウトの目的で使用されるファイルです。

fsモジュールとは

fsモジュールは、Node.jsでファイルを操作するための公式モジュールです。具体的には、ファイルの読み書きや新規作成、削除などが行えます。

一般的なフロントエンドの実装でファイルを操作する機会は少ないかもしれません。一方、サーバーサイドの実装では静的ファイルを扱う機会が多くあります。

基本的なファイル操作であれば、fsモジュールをインポートするだけで十分に事足ります。後述するように、同期・非同期処理のそれぞれに対応できる点も特徴です。

fsモジュールを使用する

それでは実践に移りましょう。前回のChapterで使用したプロジェクトを引き続き使用します。

最初に、fsモジュールで読み書きするためのテキストファイルを準備してください。

プロジェクト直下に「public」というディレクトリを作成し、その配下に「sample.txt」というファイルを追加します。

「sample.txt」には適当な文字列(例「サンプルテキストだよ」)を記述しましょう。

以下はコマンドラインで作成する場合の例です。

~/sample-app(Windows)
$ mkdir public
$ cd public
$ New-item sample.txt
$ echo "サンプルテキストだよ" > sample.txt
~/sample-app(Mac / Linux)
$ mkdir public
$ cd public
$ touch sample.txt
$ echo "サンプルテキストだよ" > sample.txt

dir_dif-ch7-1.png 現在のディレクトリ構成

ファイルの読み込み

静的ファイルを読み込むには、fs.readFileメソッドを使用します。形式は以下の通りです。

ファイルの読み込み
fs.readFile('ファイルパス', '文字コード', コールバック関数)

それでは、「app.js」に記述していきましょう。/todosに対してリクエストがあると、sample.txtの内容をコンソールに表示させるようにします。

第3引数であるコールバック関数に着目してください。引数のdataを介してファイルの内容を取得し、console.logで出力しています。

app.js
const express = require('express')
const path = require('path')
// 追記
const fs = require('fs')
const app = express()

app.set('views', path.join(__dirname, 'views'))
app.set('view engine', 'ejs')

const todos = ['おにぎりを買う', 'お茶を買う', '散歩に行く']

app.get('/todos', (req, res) => {
  // 追記
  fs.readFile('./public/sample.txt', 'utf-8', (err, data) => {
    console.log(data)
  })
  res.render('index.ejs', { todos: todos })
})

app.get('/todos/:id', (req, res) => {
  const id = req.params.id - 1
  const todo = {
    id: id + 1,
    title: todos[id],
  }
  res.render('detail.ejs', { todo: todo })
})

app.listen(3000, (req, res) => {
  console.log('Starting server on PORT: 3000')
})

編集が完了したら、コマンドライン上でnode app.jsを入力しサーバーを起動しましょう。

ブラウザから「http://localhost:3000/todos/」にアクセスします。コマンドライン上に画像のような文字列が表示されたら、静的ファイルの読み込みは成功です。

assets_load-ch7-1.png コンソール

ファイルの書き込み

今度はファイルの書き込みを実践しましょう。こちらはappendFileメソッドを使用します。呼び出しの形式は以下の通りです。

ファイルの書き込み
fs.appendFile('相対パス', '文字コード', コールバック関数)

appendFileメソッドを使うと、ファイルが存在しない場合に作成まで行なってくれます。

また、第3引数にコールバック関数を渡し、エラー処理を行うことも可能です。今回は特に追加の処理を必要としないため、第3引数は指定しません。

以下の通り「app.js」を更新します。

index.html
...省略
app.get('/todos/:id', (req, res) => {
  const id = req.params.id - 1
  const todo = {
    id: id + 1,
    title: todos[id],
  }
  // 追記
  fs.appendFile(
    './public/detail.txt',
    `id: ${todo.id} タイトル: ${todo.title}\n`
  )
  res.render('detail.ejs', { todo: todo })
})
...省略

試しに「http://localhost:3000/todos/1」に何回かアクセスしてみましょう。「detail.txt」に改行区切りでテキストが追加されていきます。

「detail.txt」を開き、アクセスした回数分の項目が書き込まれていれば成功です。

detail.txt
id: 1 タイトル: おにぎりを買う
id: 1 タイトル: おにぎりを買う
id: 1 タイトル: おにぎりを買う

readFileSyncとreadFileの違い

今回はreadFileappendFileメソッドを用いてファイルの読み書きを実装しました。ところで、fsモジュールにはよく似た名前のreadFileSyncappendFileSyncメソッドが存在します。

どちらもファイルの読み書きを行う点では同じです。しかし、末尾にSyncが付いたメソッドは「同期処理」で実行される点が異なります。

以下の表は非同期処理・同期処理の対応関係をまとめたものです。

メソッドの機能 非同期処理 同期処理
ファイルの読み取り readFile readFileSync
ファイルの書き込み appendFile appendFileSync

「同期」や「非同期」と聞いて、思わず身構えた方も多いでしょう。二つのメソッドは具体的にどのような形で処理を実行するのでしょうか。

ここからは、JavaScriptの同期・非同期処理について見ていきます。

同期処理・非同期処理の違い

結論から述べると、同期処理と非同期処理には以下の違いがあります。

説明
同期処理 プログラムの順序に従って処理が実行される。
非同期処理 ある処理の終了を待たず、プログラムの次の処理が実行される。

上記のうち、直感的に分かりやすいのは同期処理です。通常のプログラミングにおいて、関数はソースコードに書かれた順に呼び出されます。func1の処理が完全に終わらない限り、次に記述されたfunc2が呼び出されることは決してありません。

しかし、非同期処理の場合は違います。func1の終了を待たずに次のfunc2が実行されるため、ソースコードの記述順と処理が終わる順序は必ずしも一致しません。

一見して分かるように、非同期処理は取り扱いが大変です。処理終了のタイミングが未定であるため、エラー処理にも特別な構文を必要とします。

Promiseによるエラー処理

Lesson6-3「Promiseによるエラーハンドリング」では、Promise文を使ったエラーハンドリングについて詳しく学びます。

にもかかわらず、JavaScriptに非同期処理の仕組みが実装されているのは、それが時間のかかる処理に適しているためです。

Webアプリケーションでは、HTTP通信やファイルの読み書きなど、高負荷の処理を実装することが多くあります。そんなとき、通常の同期処理だと高負荷の処理を行っている間は別の処理が呼び出せません。結果として、プログラム全体の進行を止める可能性があります。

その点、非同期処理であれば、ある処理に時間がかかっていたとしても後続の処理を呼び出せます。擬似的な並列処理が可能となり、プログラム全体の進行を妨げません。

JavaScriptの非同期処理は、初心者がつまづきやすいポイントです。今すべてを理解する必要はありませんが、実践を通して把握に努めてください。。

まとめ

今回は静的ファイルを操作するためのfsモジュールを解説しました。

readFileappendFileはファイルの基本操作であるため、ここで使いこなせるようにしておきましょう。

話の本筋ではありませんが、同期処理・非同期処理の違いに関してもよく学び直すようにしてください。

次のChapterでは、引き続きfsモジュールを使用し、サーバーから画像を提供する仕組みを実装します。

Lesson 3 Chapter 8
アセットの提供

前回のChapterでは、fsモジュールによる静的ファイルの取り扱いについて学びました。

fsモジュールで操作できるのは、何もテキストファイルだけではありません。画像も静的ファイルである以上、fsモジュールで操作することが可能です。

今回のChapterでは、同じfs.readFileメソッドによる画像の読み込み方法を学びます。

fs.readFileで画像を提供する

既出ですが、ファイルを読み込むためのfs.readFileは、以下の形式で呼び出します。

fs.readFile
fs.readFile('ファイルの相対パス', コールバック関数)

第1引数にはテキストファイルだけでなく、画像ファイルのパスも指定できます。第2引数のコールバック関数を通してエラー処理を行える点も変わりません。

それでは早速コードを書いていきましょう。

ルーティングの実装

まずは画像を提供するためのルーティングをサーバー側に実装します。以下の通り、動的なエンドポイントとして/image/:idを設定しましょう。

app.js
app.get('/image/:id', (req, res) => {
  // 処理
})

画像ファイルの準備

続いて、提供する画像ファイルを準備しましょう。

「public」ディレクトリ直下に「images」ディレクトリを作成し、その配下に画像ファイルを3枚用意します。

画像素材の選定

画像はPNG形式であれば何でも構いません。適当な画像ファイルが手元にないときは、無料素材サイトなどを利用しましょう。

それぞれ以下の名前で「images」配下に保存してください。

  • onigiri.png
  • tea.png
  • sanpo.png

以下のディレクトリ構成となっていれば準備完了です。

dir_ch8-1.png

画像をレスポンスとして返却する

それでは、画像をExpressサーバーから返していきましょう。

以下の通り「app.js」を編集します。コマンドライン上でnode app.jsと入力し、サーバーを立ち上げてください。

app.js
...省略
app.get('/image/:id', (req, res) => {
  const id = req.params.id
  let path = './public/images/'
  if (Number(id) === 1) {
    path += 'onigiri.png'
  } else if (Number(id) === 2) {
    path += 'tea.png'
  } else {
    path += 'sanpo.png'
  }
  fs.readFile(path, (err, data) => {
    res.type('png')
    res.send(data)
  })
})
...省略

それでは「http://localhost:3000/image/1」にアクセスしてみましょう。「onigiri.png」に設定した画像が表示されたら成功です。

続けて「http://localhost:3000/image/2」「http://localhost:3000/image/3」にアクセスし、画像の変化を確認してください。

ここでは、本章のChapter6で紹介した動的ルートマッチングを使用しています。エンドポイントに含まれる:idの値を参照し、対応する画像をレスポンスとして返却する仕組みです。

ポイントとして、URLパラメーターはすべて文字列(String)で送信されます。:idの値を取り出すためには数字(Number)に変換しなければなりません。

また、レスポンスを返却する前にres.typeメソッドを呼び出し、画像形式(ここではPNG)を指定する必要があります。

まとめ

今回はアセット(画像)の提供を行いました。fs.readFileを使用することで、テキストだけでなく画像ファイルも操作できます。

画像をレスポンスとして返却する場合は、必ずファイル形式を指定するようにしてください。

次のChapterでは、Lessonの締めくくりとしてコードを共通化する方法について学びます。

Lesson 3 Chapter 9
部品の共通化

Lesson3では、ExpressとEJSによるWebページ作成の基本を解説してきました。

各パッケージの導入に始まり、ページの描画やルーティング、ファイル操作などを幅広く学べたことでしょう。

Lessonの最後となる今回は、共通パーツの切り分け方法について学習します。

開発の効率を進める上で、コードの共通化はとても重要です。ソースコードの記述量を減らせれば、エンジニアにかかる負担も減ります。プログラムの見通しが改善され、不用意なバグを避けることにもつながるでしょう。

開発の規模が大きくなればなるほど、この共通化のノウハウが求められるようになります。ぜひ学び始めの段階から意識するようにしてください。

部品を共通化するための準備

ここまでの実装ではコード全体の記述量が少なく、あまり共通化の恩恵を得ることができません。

そこで事前準備として、切り出すための部品を増やすことから始めましょう。

現状の実装では、全体ページとして「index.ejs」、詳細ページとして「detail.ejs」が作成されています。この二つのファイルにheaderfooterタグを追記してください。

index.ejs
...省略
  <body>
    <header>
      <h1>Todoリスト</h1>
    </header>
    <main>
      <h2>一覧</h2>
      <ul>
         { %>
        <li></li>
        
      </ul>
    </main>
    <footer>フッター</footer>
  </body>
...省略
detail.ejs
...省略
  <body>
    <header>
      <h1>Todoリスト</h1>
    </header>
    <main>
      <h2>詳細</h2>
      <p>id: </p>
      <p>タイトル: </p>
    </main>
    <footer>フッター</footer>
  </body>
...省略

headerもfooterも簡易的な内容ですが、学習目的としては十分です。ブラウザから「http://localhost/todos」にアクアスし、実際に適用されているか確認してみましょう。

todo_list_ch9-1.png http://localhost:3000/todos

todo_detail_ch9-1.png http://localhost:3000/todos/1

ディレクトリの作成

続いて、共通部品を置くためのディレクトリを作成します。

プロジェクト内の「views」配下に「_share」ディレクトリを作成しましょう。部品のディレクトリであることを示すため「_」を先頭につけています。

ファイルの作成

あわせて、共通化するファイルを先に作成しておきましょう。「_share」配下に以下の4ファイルを作成してください。

  • head.ejs
  • header.ejs
  • pageTitle.ejs
  • footer.ejs

views_ch9-1.png views

以上で事前準備は完了です。ここから共通パーツを抜き出していきましょう。

EJSのテンプレートを共通化する

EJSのテンプレートのうち、共通化できる部品を別ファイルに抜き出します。

Lesson1-4「EJSとは」でも少し触れましたが、EJSのinclude関数を用いるとファイル間での呼び出しが可能です。

このincludeを活用し、4つの共通部品を抜き出していきましょう。

共通化:headタグ

各ページのheadタグは同一の構成になることが多く、真っ先に共通化が検討されます。

事前準備で作成した「_share/head.ejs」に以下を記述していきましょう。

head.ejs
<head>
  <meta charset="UTF-8" />
  <meta http-equiv="X-UA-Compatible" content="IE=edge" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>Todo</title>
</head>

「head.ejs」の部品を呼び出すためには、「index.ejs」を編集して以下の通り記述します。

index.ejs
<html lang="en">
  
  <body>...省略

同様に、「detail.ejs」からも「head.ejs」のincludeを行なってください。

画面で実際に確認してみましょう。ブラウザから「http://localhost:3000/todos」にアクセスしてみてください。

headタグの内容は画面に表示されないため、きちんと適用されているか確かめるには各ブラウザのデベロッパーツールを使うと便利です。

参考:Google Chrome デベロッパーツールによる確認方法

  • http://localhost:3000/todos」にアクセスする
  • 画面を右クリックして「検証」をクリックする
  • メニューの中から「Element」を選択する(HTML構造が表示される)
  • headタグが適用されているか確認する。

include_inspect-ch9-1.png headタグの適用確認

共通化:header・footerタグ

続いてheaderタグ(注:headタグとは異なります)を共通部品として切り出します。切り出した内容は「header.ejs」に記述していきましょう。

header.ejs
<header>
  <h1>Todoリスト</h1>
</header>

また、footerも分割します。こちらの内容は「footer.ejs」に記述してください。

footer.ejs
<footer>フッター</footer>

二つの部品を呼び出すため、「index.ejs」と「detail.ejs」に以下の変更を適用します。

index.ejs
<!DOCTYPE html>
<html lang="en">
  
  <body>
    
    <main>
      <h2>Todo一覧</h2>
      <ul>
         { %>
        <li></li>
        
      </ul>
    </main>
    
  </body>
</html>
detail.ejs
<!DOCTYPE html>
<html lang="en">
  
  <body>
    
    <main>
      <h2>詳細</h2>
      <p>id: </p>
      <p>タイトル: </p>
    </main>
    
  </body>
</html>

先ほどと同様に、ブラウザから確認して見ましょう。タグが正しく適用されていれば成功です。

共通化:pageTitle

最後に、h2タグの部分を共通化しましょう。

ここでの問題として、「index.ejs」と「detail.ejs」ではh2タグの文言が異なる点が挙げられます。(前者は「Todo一覧」であるのに対し後者は「詳細」)。

このような場合、include時に変数を渡すことで値を変更できます。

index.ejs
include('相対パス', 引数)

上記の通り、変数を渡す時は第2引数を利用します。

まずは共通化ファイル「pageTitle.ejs」の作成からです。

pageTitle.ejs
<h2></h2>

呼び出すためには、「index.ejs」と「detail.ejs」で以下のように記述します。それぞれtitleの値が異なる点に注目してください。

index.ejs
...省略
<main>
      
      <ul>
...省略
detail.ejs
...省略
<main>
      
      <ul>
...省略

以上で部品の共通化が完了しました。ブラウザで「http://localhost:3000/todos」にアクセスし、画面を確認してみましょう。

正しくページが表示されていれば成功です。

include_done-ch9-1.png http://localhost:3000/todos

include_done-ch9-2.png http://localhost:3000/todos/1

まとめ

今回はEJSのinclude機能を使用し、部品の共通化を行いました。

Webアプリケーションの開発では、このように同一の記述を使い回す場面が少なくありません。

統一したスタイルを適用するときや、今回のようにheader・footerタグを使うときなど、部品の共通化により得られるメリットは多大です。

また、部品の共通化は「コンポーネント指向」の考え方にも関わってきます。コンポーネント志向とは、アプリケーションを小さな部品に分割し、各部を組み合わせて開発を進める手法です。

「React」や「Vue.js」といったJavaScriptのフレームワークも、このコンポーネント指向を設計思想としています。皆さんがWebアプリケーション開発に携わる上で、避けて通れない概念といえるでしょう。