Linux Professional Institute Learning Logo.
main contentにスキップ
  • ホーム
    • 全てのリソース
    • LPI学習教材
    • コントリビューターになる
    • Publishing Partners
    • Publishing Partnerになる
    • About
    • FAQ
    • コントリビューター
    • Roadmap
    • 連絡先
  • LPI.org
035.2 レッスン 2
課題 031: ソフトウェア開発とWebテクノロジー
031.1 ソフトウェア開発の基本
  • 031.1 レッスン 1
031.2 Webアプリケーションのアーキテクチャ
  • 031.2 レッスン 1
031.3 HTTP の基礎
  • 031.3 レッスン 1
課題 032: HTMLドキュメントマークアップ
032.1 HTMLドキュメントの構造
  • 032.1 レッスン 1
032.2 HTMLのセマンティックスとドキュメント階層
  • 032.2 レッスン 1
032.3 HTMLにおける参照と埋め込みリソース
  • 032.3 レッスン 1
032.4 HTMLフォーム
  • 032.4 レッスン 1
課題 033: CSSコンテンツ スタイリング
033.1 CSSの基本
  • 033.1 レッスン 1
033.2 CSSセレクターとスタイルアプリケーション
  • 033.2 レッスン 1
033.3 CSSスタイリング
  • 033.3 レッスン 1
033.4 CSSボックスモデルとレイアウト
  • 033.4 レッスン 1
課題 034: JavaScriptプログラミング
034.1 JavaScriptの実行と構文
  • 034.1 レッスン 1
034.2 JavaScriptデータ構造
  • 034.2 レッスン 1
034.3 JavaScriptの制御構造と関数
  • 034.3 レッスン 1
  • 034.3 レッスン 2
034.4 WebサイトのコンテンツとスタイリングのJavaScript操作
  • 034.4 レッスン 1
課題 035: NodeJSサーバープログラミング
035.1 NodeJSの基本
  • 035.1 レッスン 1
035.2 NodeJS Expressの基本
  • 035.2 レッスン 1
  • 035.2 レッスン 2
035.3 SQLの基本
  • 035.3 レッスン 1
How to get certified
  1. 課題 035: NodeJSサーバープログラミング
  2. 035.2 NodeJS Expressの基本
  3. 035.2 レッスン 2

035.2 レッスン 2

Certificate:

Web開発の要点

Version:

1.0

Topic:

035 NodeJSサーバプログラミング

Objective:

035.2 NodeJS Expressの基本

Lesson:

2 of 2

はじめに

Webサーバは、クライアントのリクエストに対するレスポンスを生成するために、非常に多彩なメカニズムを備えています。要求されたリソースがどのクライアントにとっても同じであるため、Webサーバが処理されない静的な応答を提供するだけで十分な場合もあります。例えば、誰もがアクセスできる画像をクライアントが要求した場合、サーバは画像を含むファイルを送信すれば十分です。

しかし、レスポンスが動的に生成される場合には、サーバスクリプトに書かれた単純な行よりも優れた構造が必要になることがあります。このような場合には、Webサーバが完全なドキュメントを生成し、それをクライアントが解釈して表示できるようにすると便利です。Webアプリケーションの開発では、HTMLドキュメントはテンプレートとして作成され、サーバスクリプトとは別に管理されます。サーバスクリプトは、適切なテンプレートの所定の場所に動的データを挿入し、フォーマットされたレスポンスをクライアントに送信します。

Webアプリケーションは、しばしば静的リソースと動的リソースの両方を消費します。HTMLドキュメントは、たとえそれが動的に生成されたものであっても、CSSファイルや画像などの静的リソースへの参照を持つ場合があります。Expressがこのような需要にどのように対応するかを説明するために、まず、静的ファイルを配信するサンプルサーバをセットアップし、次に、構造化されたテンプレートベースのレスポンスを生成するルートを実装します。

静的ファイル

最初のステップは、サーバとして動作するJavaScriptファイルを作成することです。以前のレッスンで取り上げた同じパターンで、シンプルなExpressアプリケーションを作ってみましょう。まず、 server というディレクトリを作成し、 npm コマンドで基本コンポーネントをインストールします。

$ mkdir server
$ cd server/
$ npm init
$ npm install express

エントリポイントには、任意のファイル名を使用できますが、ここではデフォルトのファイル名である index.js を使用します。以下のリストは、サーバのスタートポイントとして使用する基本的な index.js ファイルを示しています。

const express = require('express')
const app = express()
const host = "myserver"
const port = 8080

app.listen(port, host, () => {
  console.log(`Server ready at http://${host}:${port}`)
})

静的ファイルを送信するのに、明示的なコードを書く必要はありません。Expressには、この目的のために、 express.static というミドルウェアが用意されています。サーバがクライアントに静的ファイルを送信する必要がある場合は、スクリプトの最初に express.static というミドルウェアをロードするだけです。

app.use(express.static('public'))

public パラメータは、クライアントがリクエストできるファイルを格納するディレクトリを示します。クライアントがリクエストするパスには、 public ディレクトリを含めてはならず、ファイル名か、 public ディレクトリからの相対パスのみを指定します。例えば、 public/layout.css というファイルをリクエストするには、クライアントは /layout.css にリクエストを行います。

フォーマットされた出力

静的コンテンツの送信は簡単ですが、動的に生成されたコンテンツは大きく変化します。短いメッセージで動的なレスポンスを作成することで、開発の初期段階にあるアプリケーションを簡単にテストすることができます。例えば、HTTPの POST メソッドで送信されたメッセージをクライアントに送り返すだけのテストルートを以下に示します。レスポンスには、メッセージの内容をフォーマットなしのプレーンテキストで再現することができます。

app.post('/echo', (req, res) => {
  res.send(req.body.message)
})

このようなルートは、Expressの学習時や診断目的で使用するのに適した例で、 res.send() で送られる生のレスポンスで十分です。しかし、便利なサーバは、より複雑なレスポンスを生成できなければなりません。ここでは、そのようなより洗練されたタイプのルートの開発に移ります。

私たちの新しいアプリケーションは、現在のリクエストの内容を単に送り返すのではなく、各クライアントが以前のリクエストで送ったメッセージの完全なリストを保持し、リクエストがあれば各クライアントのリストを送り返します。すべてのメッセージをマージした応答もオプションとして用意されていますが、特に応答がより精巧になるにつれて、他のフォーマットされた出力モードがより適切になります。

現在のセッション中に送信されたクライアントのメッセージを受信して保存するためには、まず、クッキーやHTTPの POST メソッドで送信されたデータを処理するための追加モジュールを組み込む必要があります。以下のサンプルサーバの目的は、 POST 経由で送信されたメッセージを記録し、クライアントが GET リクエストを発行したときに以前に送信されたメッセージを表示することだけです。そのため、 / パスには2つのルートがあります。最初のルートはHTTPの POST メソッドで行われたリクエストを満たし、2番目のルートはHTTPの GET メソッドで行われたリクエストを満たします。

const express = require('express')
const app = express()
const host = "myserver"
const port = 8080

app.use(express.static('public'))

const cookieParser = require('cookie-parser')
app.use(cookieParser())

const { v4: uuidv4 } = require('uuid')

app.use(express.urlencoded({ extended: true }))

// Array to store messages
let messages = []

app.post('/', (req, res) => {

  // Only JSON enabled requests
  if ( req.headers.accept != "application/json" )
  {
    res.sendStatus(404)
    return
  }

  // Locate cookie in the request
  let uuid = req.cookies.uuid

  // If there is no uuid cookie, create a new one
  if ( uuid === undefined )
    uuid = uuidv4()

  // Add message first in the messages array
  messages.unshift({uuid: uuid, message: req.body.message})

  // Collect all previous messages for uuid
  let user_entries = []
  messages.forEach( (entry) => {
    if ( entry.uuid == req.cookies.uuid )
      user_entries.push(entry.message)
  })

  // Update cookie expiration date
  let expires = new Date(Date.now());
  expires.setDate(expires.getDate() + 30);
  res.cookie('uuid', uuid, { expires: expires })

  // Send back JSON response
  res.json(user_entries)

})

app.get('/', (req, res) => {

  // Only JSON enabled requests
  if ( req.headers.accept != "application/json" )
  {
    res.sendStatus(404)
    return
  }

  // Locate cookie in the request
  let uuid = req.cookies.uuid

  // Client's own messages
  let user_entries = []

  // If there is no uuid cookie, create a new one
  if ( uuid === undefined ){
    uuid = uuidv4()
  }
  else {
    // Collect messages for uuid
    messages.forEach( (entry) => {
      if ( entry.uuid == req.cookies.uuid )
        user_entries.push(entry.message)
    })
  }

  // Update cookie expiration date
  let expires = new Date(Date.now());
  expires.setDate(expires.getDate() + 30);
  res.cookie('uuid', uuid, { expires: expires })

  // Send back JSON response
  res.json(user_entries)

})

app.listen(port, host, () => {
  console.log(`Server ready at http://${host}:${port}`)
})

静的ファイルの設定を一番上にしているのは、 layout.css のような静的ファイルを用意するとすぐに便利だからです。前の章で紹介した cookie-parser ミドルウェアに加えて、このサンプルでは、メッセージを送信する各クライアントにクッキーとして渡される固有の識別番号を生成する uuid ミドルウェアも含まれています。これらのモジュールは、サンプルサーバのディレクトリにインストールされていなければ、コマンド npm install cookie-parser uuid でインストールできます。

messages というグローバル配列には、すべてのクライアントが送信したメッセージが格納されます。この配列の各アイテムは、プロパティ uuid と message を持つオブジェクトで構成されています.

このスクリプトの新機能は、2つのルートの最後に使用されている res.json() メソッドで、クライアントから既に送信されたメッセージを含む配列を含むJSON形式のレスポンスを生成することです。

// Send back JSON response
res.json(user_entries)

JSONはプレーンテキスト形式で、一連のデータを一つの構造にまとめることができます。この構造は連想的で、内容はキーと値で表現されます。JSONは、レスポンスがクライアントで処理される場合に特に有効です。このフォーマットを使用すると、JavaScriptのオブジェクトや配列を、サーバ上の元のオブジェクトのすべてのプロパティとインデックスを使って、クライアント側で簡単に再構築することができます。

各メッセージをJSONで構成しているため、 accept ヘッダーに application/json が含まれていないリクエストは拒否しています。

// Only JSON enabled requests
if ( req.headers.accept != "application/json" )
{
  res.sendStatus(404)
  return
}

curl はデフォルトで accept ヘッダーに application/json を指定しないため、新しいメッセージを挿入するためにプレーンな curl コマンドで行われたリクエストは受け入れられません。

$ curl http://myserver:8080/ --data message="My first message" -c cookies.txt -b cookies.txt
Not Found

-H "accept: application/json" オプションは、リクエストヘッダーを変更してレスポンスのフォーマットを指定するもので、今回は指定されたフォーマットで受け入れて応答します。

$ curl http://myserver:8080/ --data message="My first message" -c cookies.txt -b cookies.txt -H "accept: application/json"
["My first message"]

他のルートを使ってメッセージを取得することも同様の方法で行われますが、今回はHTTPの GET メソッドを使用します。

$ curl http://myserver:8080/ -c cookies.txt -b cookies.txt -H "accept: application/json"
["Another message","My first message"]

テンプレート

JSONのような形式のレスポンスは、プログラム間の通信には便利ですが、ほとんどのWebアプリケーションサーバの主な目的は、人間が消費するためのHTMLコンテンツを生成することです。同じファイルの中に言語が混在していると、プログラムがエラーを起こしやすくなり、コードの保守性が損なわれるため、JavaScriptのコードの中にHTMLのコードを埋め込むことは良いアイデアではありません。

Expressは、ダイナミックコンテンツ用のHTMLを分離するさまざまな テンプレートエンジン と連携することができます。その全リストは、https://expressjs.com/en/resources/template-engines.html[Express template engines site]に掲載されています。最も人気のあるテンプレート・エンジンのひとつが Embedded JavaScript (EJS)で、動的コンテンツを挿入するための特定のタグを持つHTMLファイルを作成することができます。

他のExpressコンポーネントと同様に、EJSはサーバが稼働しているディレクトリにインストールする必要があります。

$ npm install ejs

次に、サーバスクリプトでEJSエンジンをデフォルトのレンダラーとして設定する必要があります( index.js ファイルの先頭付近、ルート定義の前)。

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

テンプレートを使って生成されたレスポンスは、 res.render() 関数を使ってクライアントに送信されます。この関数は、テンプレートのファイル名と、テンプレート内からアクセス可能な値を含むオブジェクトをパラメータとして受け取ります。前述の例で使用したルートは、JSONだけでなく、HTMLレスポンスを生成するように書き換えることもできます。

app.post('/', (req, res) => {

  let uuid = req.cookies.uuid

  if ( uuid === undefined )
    uuid = uuidv4()

  messages.unshift({uuid: uuid, message: req.body.message})

  let user_entries = []
  messages.forEach( (entry) => {
    if ( entry.uuid == req.cookies.uuid )
      user_entries.push(entry.message)
  })

  let expires = new Date(Date.now());
  expires.setDate(expires.getDate() + 30);
  res.cookie('uuid', uuid, { expires: expires })

  if ( req.headers.accept == "application/json" )
    res.json(user_entries)
  else
    res.render('index', {title: "My messages", messages: user_entries})

})

app.get('/', (req, res) => {

  let uuid = req.cookies.uuid

  let user_entries = []

  if ( uuid === undefined ){
    uuid = uuidv4()
  }
  else {
    messages.forEach( (entry) => {
      if ( entry.uuid == req.cookies.uuid )
        user_entries.push(entry.message)
    })
  }

  let expires = new Date(Date.now());
  expires.setDate(expires.getDate() + 30);
  res.cookie('uuid', uuid, { expires: expires })

  if ( req.headers.accept == "application/json" )
    res.json(user_entries)
  else
    res.render('index', {title: "My messages", messages: user_entries})

})

レスポンスのフォーマットは、リクエストに含まれる accept ヘッダーに依存することに注意してください。

if ( req.headers.accept == "application/json" )
  res.json(user_entries)
else
  res.render('index', {title: "My messages", messages: user_entries})

JSON形式のレスポンスは、クライアントが明示的にリクエストした場合にのみ送信されます。それ以外の場合は、 index テンプレートからレスポンスが生成されます。同じ user_entries 配列が、JSON出力とテンプレートの両方に供給されますが、後者のパラメータとして使用されるオブジェクトは、 title: "My messages" プロパティがあり、これがテンプレート内でタイトルとして使用されます。

HTMLテンプレート

静的ファイルと同様に、HTMLテンプレートを含むファイルは、それぞれのディレクトリに存在します。デフォルトでは、EJSはテンプレートファイルが views/ ディレクトリにあると仮定します。この例では、 index という名前のテンプレートが使われているので、EJSは views/index.ejs というファイルを探します。以下のリストは、サンプルコードで使用できるシンプルな views/index.ejs テンプレートの内容です。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title><%= title %></title>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="stylesheet" href="/layout.css">
</head>
<body>

<div id="interface">

<form action="/" method="post">
<p>
  <input type="text" name="message">
  <input type="submit" value="Submit">
</p>
</form>

<ul>
<% messages.forEach( (message) => { %>
<li><%= message %></li>
<% }) %>
</ul>

</div>

</body>
</html>

最初の特別なEJSタグは、 <head> セクションの <title> 要素です。

<%= title %>

レンダリングの際には、この特別なタグは、 res.render() 関数のパラメータとして渡されたオブジェクトの title プロパティの値で置き換えられます。

テンプレートの大部分は従来のHTMLコードで構成されていますので、テンプレートには新しいメッセージを送信するためのHTMLフォームが含まれています。テストサーバはHTTPの GET メソッドと POST メソッドに対して同じパス / で応答しますので、formタグには action="/" と method="post" 属性が付いています。

テンプレートの他の部分は、HTMLコードとEJSのタグが混在しています。EJSは、テンプレート内の特定の目的のためのタグを持っています。

<% … %>

フロー制御を挿入します。このタグによって直接コンテンツが挿入されることはありませんが、JavaScriptの構造体とともに使用することで、HTMLのセクションを選択したり、繰り返したり、抑制したりすることができます。ループを開始する例: <% messages.forEach( (message) => { %>

<%# … %>

コメントを定義します。その内容はパーサーによって無視されます。HTMLで書かれたコメントとは異なり、これらのコメントはクライアントからは見えません。

<%= … %>

変数のエスケープされた内容を挿入します。クロスサイトスクリプティング(XSS)攻撃の抜け道となるJavaScriptコードの実行を避けるために、未知のコンテンツをエスケープすることが重要です。例: <%= title %>

<%- … %>

エスケープせずに変数の内容を挿入します。

HTMLコードとEJSタグが混在していることは、クライアントのメッセージがHTMLリストとしてレンダリングされているスニペットを見れば明らかです。

<ul>
<% messages.forEach( (message) => { %>
<li><%= message %></li>
<% }) %>
</ul>

このスニペットでは、最初の <% …​ %> タグが forEach 文を開始し、 message 配列のすべての要素をループしています。 <% および %> の区切り文字は、HTMLのスニペットを制御することができます。messages の各要素に対して、 <li><%= message %></li> という新しいHTMLリストアイテムが生成されます。これらの変更により、以下のようなリクエストを受信した場合、サーバはHTMLでレスポンスを送信するようになります。

$ curl http://myserver:8080/ --data message="This time" -c cookies.txt -b cookies.txt
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>My messages</title>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="stylesheet" href="/layout.css">
</head>
<body>

<div id="interface">

<form action="/" method="post">
<p>
  <input type="text" name="message">
  <input type="submit" value="Submit">
</p>
</form>

<ul>

<li>This time</li>

<li>in HTML</li>

</ul>

</div>

</body>
</html>

リクエストを処理するコードとレスポンスを表示するコードを分離することで、コードがすっきりし、チーム内で専門性の異なる人がアプリケーションの開発を分担できるようになります。例えば、Webデザイナーは、 views/ にあるテンプレートファイルと、関連するスタイルシートに注目することができます。これらは、サンプルサーバの public/ ディレクトリに格納された静的ファイルとして提供されます。

演習

  1. クライアントが assets ディレクトリ内のファイルをリクエストできるようにするには、 express.static をどのように設定すればよいですか?

  2. リクエストのヘッダーに指定されているレスポンスのタイプは、Expressルートの中でどのように識別できますか?

  3. res (レスポンス)ルートパラメーターのどのメソッドが、 content というJavaScriptの配列からJSON形式のレスポンスを生成しますか?

発展演習

  1. デフォルトでは、Expressのテンプレートファイルは views ディレクトリにあります。テンプレートファイルが templates に格納されるように、この設定を変更するにはどうすればよいでしょうか。

  2. クライアントがタイトルのないHTMLレスポンスを受け取ったとします(つまり、 <title></title> )。EJS テンプレートを検証した後、開発者はファイルの head セクションに <title><% title %></title> タグを見つけました。この問題の原因は何だと思われますか?

  3. EJSのテンプレートタグを使って、JavaScriptの変数 h2 の内容を持つ <h2></h2> のHTMLタグを書いてください。このタグは、変数 h2 が空でない場合にのみレンダリングされる必要があります。

まとめ

このレッスンでは、Express.jsが提供する、静的なレスポンスや整形された動的なレスポンスを生成するための基本的な方法について説明しました。静的なファイルのためのHTTPサーバをセットアップするための労力はほとんど必要ありません。また、EJSのテンプレート・システムは、HTMLファイルから動的コンテンツを生成するための簡単な方法を提供します。このレッスンでは、以下の概念と手順を説明しました。

  • 静的ファイルのレスポンスに express.static を使用する方法

  • リクエストヘッダーのコンテンツタイプフィールドにマッチするレスポンスを作成する方法

  • JSON 構造のレスポンス

  • HTMLベースのテンプレートでEJSタグを使用する方法

演習の回答

  1. クライアントが assets ディレクトリ内のファイルをリクエストできるようにするには、 express.static をどのように設定すればよいですか?

    サーバスクリプトには、 app.use(express.static('assets')) の呼び出しを追加する必要があります。

  2. リクエストのヘッダーに指定されているレスポンスのタイプは、Expressルートの中でどのように識別できますか?

    クライアントは、 req.headers.accept プロパティに対応する accept ヘッダーフィールドで、受け入れ可能なタイプを設定します。

  3. res (レスポンス)ルートパラメーターのどのメソッドが、 content というJavaScriptの配列からJSON形式のレスポンスを生成しますか?

    res.json() メソッド: res.json(content) 。

発展演習の回答

  1. デフォルトでは、Expressのテンプレートファイルは views ディレクトリにあります。テンプレートファイルが templates に格納されるように、この設定を変更するにはどうすればよいでしょうか。

    このディレクトリは、スクリプトの初期設定で app.set('views', './templates') で定義できます。

  2. クライアントがタイトルのないHTMLレスポンスを受け取ったとします(つまり、 <title></title> )。EJS テンプレートを検証した後、開発者はファイルの head セクションに <title><% title %></title> タグを見つけました。この問題の原因は何だと思われますか?

    <%= %> タグは、 <%= title %> のように、変数の内容を囲むために使用します。

  3. EJSのテンプレートタグを使って、JavaScriptの変数 h2 の内容を持つ <h2></h2> のHTMLタグを書いてください。このタグは、変数 h2 が空でない場合にのみレンダリングされる必要があります。

    <% if ( h2 != "" ) { %>
    <h2><%= h2 %></h2>
    <% } %>

Linux Professional Insitute Inc. All rights reserved. 学習資料をご覧ください: https://learning.lpi.org
ここでの作成物は、Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International Licenseの下でライセンスされています。

次のレッスン

035.3 SQLの基本 (035.3 レッスン 1)

次のレッスンを読む

Linux Professional Insitute Inc. All rights reserved. 学習資料をご覧ください: https://learning.lpi.org
ここでの作成物は、Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International Licenseの下でライセンスされています。

LPIは非営利団体です。

© 2023 Linux Professional Institute(LPI)は、オープンソースプロフェッショナル向けのグローバルな認定基準およびキャリアサポート組織です。200,000人以上の認定保持者を擁する、世界初かつ最大のベンダー中立Linuxおよびオープンソース認定機関です。LPIは180か国以上で認定プロフェッショナルを擁し、複数の言語で試験を実施し、何百ものトレーニングパートナーを擁しています。

私たちの目的は、オープンソースの知識とスキルの認定資格を世界中からアクセスできるようにすることで、誰にとっても経済的で創造的な機会を可能にすることです。

  • LinkedIn
  • flogo-RGB-HEX-Blk-58 Facebook
  • Twitter
  • お問い合わせ
  • 個人情報とCookieポリシー

間違いを見つけたり、このページを改善したいですか? 私たちに知らせてください。.

© 1999–2023 The Linux Professional Institute Inc. All rights reserved.