SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

CoffeeScriptによるモダンなWebアプリケーション開発

CoffeeScriptベストプラクティス集
Node.jsアプリケーション編(2)

CoffeeScriptによるモダンなWebアプリケーション開発 第4回

  • X ポスト
  • このエントリーをはてなブックマークに追加

Webアプリケーションの開発

 Webアプリケーションを開発する時、すべてを一から開発するのは大変です。幸いなことにNode.jsにはExpress(MITライセンス)というWebフレームワークがあり、これを土台として楽にWebアプリケーションを開発できます。なお、本稿は執筆時点での最新バージョンであるExpress 3.0に基づいています。

 Expressを使うと、たった5行のソースコードで最小限のWebサーバを作ることができます(リスト18)。

[リスト18]Expressを使った最小限のWebサーバ
express = require 'express'
app = express.createServer()
app.get '/', (req, res) ->
  res.send "Hello, world!"
app.listen 3000

インストール

 Express 3.0をインストールするには、プロジェクトのディレクトリに移動して次のコマンドを実行します。

$ npm install express@3.0

 また、後ほど使うEcoとConsolidate.js(MITライセンス)というモジュールもインストールしておきます。

$ npm install eco consolidate

ファイル構成

 ディレクトリやファイルの構成は自由に作ることができますが、以下に参考例を挙げます。

  • Makefile:ビルド用のファイル
  • README:アプリケーションの説明
  • config/:環境設定ファイルを置く
  • src/:CoffeeScriptなどのソースコードを置く
  • lib/:コンパイルしたJavaScriptを置く(本番環境などで使用)
  • public/:静的ファイルを置く
  • test/:テストコードを置く
  • views/:HTMLなどのテンプレートを置く
  • log/:ログファイルの出力先

 config/config.coffeeには環境設定ファイルを置きます。開発環境と本番環境とをすぐに切り替えられるよう、環境設定などを記述しておきます。設定ファイルとしてまとめておくことで、プログラムに埋め込んでおくよりも編集しやすくなります。ファイル内容の参考例をリスト19に示します。もしデータベースのパスワードなどを書いた場合、誤って公開してしまうことがないように気をつけてください。

[リスト19]config/config.coffee
module.exports =
  environment: 'development'
  mysql:
    host: '127.0.0.1'
    port: 3306
    database: 'dbname'
    username: 'dbuser'
    password: 'dbpassword'

 Webサーバのメインプログラムをsrc/app.coffeeとし、リスト20の内容で作成します。

[リスト20]src/app.coffee
config  = require '../config/config'
express = require 'express'
cons    = require 'consolidate'

app = express.createServer()
app.configure ->
  # 環境設定
  app.use express.logger()  # ログを出力
  app.use express.static "#{__dirname}/../public"  # 静的ファイル
  app.use express.bodyParser()  # GETやPOSTデータを自動的に解釈
  app.set 'views', "#{__dirname}/../views"  # テンプレートの場所
  app.set 'view engine', 'eco'  # デフォルトのビューエンジンを設定
  app.engine 'eco', cons.eco  # 拡張子ecoをEcoテンプレートとして解釈

# ここにパスと処理の対応(ルーティング)を追加していく

# / へのGETリクエストが来た時
app.get '/', (req, res) ->
  res.render 'index.eco'

# どのパスにもマッチしなかった場合
app.all '*', (req, res) ->
  res.send 404

app.listen 3000
console.log "Server has started."

 そして、viewsディレクトリにindex.ecoというファイルを置きます(リスト21)。

[リスト21]views/index.eco
<!doctype html>
<html lang="ja">
<head>
  <meta charset="utf-8">
  <title>Hello</title>
</head>
<body>
  <p>Hello, World!</p>
</body>
</html>

 ここまでできたらターミナルからsrc/app.coffeeを起動してみましょう。

$ coffee src/app.coffee
Server has started.

 上記のようにメッセージが出ればサーバの起動は成功です。ブラウザでhttp://127.0.0.1:3000/を開くと「Hello, World!」と表示されます。

 これでExpressを使ったアプリケーションの基礎部分ができました。あとは必要に応じてコンテンツを追加していきましょう。src/app.coffeeを変更した時は、上記のコマンドを一旦停止して再実行すると反映されます。

クエリストリングを受け取る

 GETでクエリストリングを受け取るには、ルーティングとしてリスト22のように記述します。パラメータとして受け取る箇所を「:パラメータ名」で指定します。

[リスト22]クエリストリングを受け取る
app.get '/blog/:category/:slug', (req, res) ->
  res.write "category: #{req.params.category}\r\n"
  res.write "slug: #{req.params.slug}\r\n"
  res.end()   # レスポンスが完了したことを示す

 このように定義しておくと、ユーザーがブラウザから/blog/tech/coffeescript-guideというパスにアクセスした場合、req.params.categoryの値がtech、req.params.slugの値がcoffeescript-guideとなります。

POSTデータを受け取る

 POSTデータを受け取るにはリスト23のように記述します。

[リスト23]POSTデータを受け取る
app.post '/example', (req, res) ->
  res.contentType 'text/plain; charset=utf-8'
  res.write "abc: #{req.body.abc}\r\n"
  res.write "def: #{req.body.def}\r\n"
  res.end()

 src/app.coffee内でapp.use express.bodyParser()を実行しているため、POSTされたデータは自動的にオブジェクトに変換されてreq.bodyのプロパティとしてアクセスできるようになります。例えばabc=123&def=456というデータを送るとreq.body.abcに123が、req.body.defに456がセットされます。

 また、リクエストヘッダにContent-Type: application/jsonが含まれる場合はJSONとして解釈され、POSTデータとして{"abc":"123","def":"456"}という文字列を送るとreq.body.abcが123、req.body.defが456という値になります。

静的ファイル

 publicディレクトリに置いたファイルはすべて静的ファイルとしてブラウザからアクセスできます。例えば、public/favicon.icoに置いたファイルはhttp://ホスト/favicon.icoというURLでアクセスできます。

その他のメソッド

 Expressで使えるその他のメソッドはAPIドキュメント(英語)を参照してください。

テンプレートエンジンEco

 Expressでは好みのテンプレートエンジンを選んで使うことができますが、本稿ではCoffeeScriptと相性の良いEco(MITライセンス)というテンプレートエンジンを使います。

 Ecoのテンプレートはviewsディレクトリ内に置きます。拡張子は.ecoを使います。app/src.coffeeの中でres.render()の第1引数にテンプレート名を指定します。第2引数にオブジェクトを指定するとそのプロパティをテンプレート内で@プロパティ名で参照できるようになります(リスト24、25)。

[リスト24]テンプレートの呼び出し部分(src/app.coffee)
res.render 'index.eco',
  name:  "矢野太郎"
  email: "taro@example.com"
[リスト25]テンプレート内で変数を使う(views/index.eco)
<div>
  <span id="name"><%= @name %></span>
  <span id="email"><%= @email %></span>
</div>

 出力されるHTMLは次のようになります。なお、src/app.coffeeの変更を反映するにはコマンドを一旦止めて再実行します。

<div>
  <span id="name">矢野太郎</span>
  <span id="email">taro@example.com</span>
</div>

 なお、src/app.coffeeでapp.set 'view engine', 'eco'を実行しているため、テンプレートファイル名の拡張子を省略してres.render 'index'と記述することもできます。

テンプレートの書式

 Ecoのテンプレートで使える書式は次の通りです。

<% 式 %>

 式を評価します。任意のCoffeeScriptを実行できます。

<%= 式 %>

 式を評価し、その値をエスケープして出力します。

<%- 式 %>

 式を評価し、その値をエスケープせずに出力します。

<%= @プロパティ %>

 render()の第2引数で渡されたオブジェクトのプロパティを出力します。

<%= @helper() %>

 render()の第2引数で渡されたオブジェクトのhelperメソッドを呼び、その戻り値をエスケープして出力します。

<% @helper -> %>...<% end %>

 render()の第2引数で渡されたオブジェクトのhelperメソッドを呼びます。helperが実行される際、第1引数にキャプチャ関数が渡されます。そのキャプチャ関数を実行すると、...に該当する部分のコンテンツを戻り値として受け取ることができます。

<%%

 <%という文字を出力します。

<% if 式: %>
...
<% else if 式: %>   (省略可)
...
<% else: %>          (省略可)
...
<% end %>

 CoffeeScriptのプログラムと同じようにif-elseによる条件分岐で該当する部分のコンテンツを出力します。

<% for person in @persons: %>
  <ul>
    <li>名前: <%= person.name %></li>
    <li>国: <%= person.country %></li>
  </ul>
<% end %>

 CoffeeScriptのfor...inと同じように、配列の各要素について<% end %>までのコンテンツを繰り返し出力します。

レスポンスを圧縮する

 レスポンスをgzipやdeflateで圧縮する場合は、compressというミドルウェアを使います。

 compressを使うには、app.configure()部分のexpress.static()の直前に次の行を追加します。この設定をすると、リクエストヘッダのAccept-Encodingにgzipやdeflateが含まれている場合にコンテンツを圧縮して転送します。

app.use express.compress()

静的ファイルをブラウザにキャッシュさせる

 一般的に画像などの静的ファイルの更新頻度は低いため、ブラウザ側でキャッシュさせるようにするとネットワークの使用量を節約できます。静的ファイルをブラウザ側でキャッシュさせるには、express.static()を呼ぶ時にmaxAgeオプションを指定します。前述のcompressや他のミドルウェアと組み合わせると、app.configure()内は次のようなコードになります。

app.configure ->
  app.use express.logger()
  app.use express.compress()
  app.use express.static "#{__dirname}/../public",
    maxAge: 365 * 24 * 60 * 60 * 1000
  app.use express.bodyParser()
  app.set 'view engine', 'eco'
  app.engine 'eco', cons.eco

 なお、このようにしてブラウザにキャッシュさせたコンテンツの内容を更新したときは、HTMLから読み込むURLを変更すると、ブラウザが新たにコンテンツを取得するようになります。

独自のエラーページを用意する

 Not FoundやInternal Server Errorなどのエラーページを独自に用意するには、まずsrc/app.coffeeのファイルの先頭で次のようにNotFoundクラスを定義します。

class NotFound extends Error
  constructor: (msg) ->
    @name = 'NotFound'
    Error.call this, msg
    Error.captureStackTrace this, NotFound

 次に、app.get()などのルーティング定義部分より後で、次のようにミドルウェアを追加しエラーを拾うようにしておきます。

app.configure ->
  # エラーを拾うためのミドルウェアを追加する
  app.use (err, req, res, next) ->
    if err instanceof NotFound  # NotFoundの場合
      res.statusCode = 404
      res.render '404'  # views/404.eco を使う
    else  # NotFound以外のエラーの場合は500とする
      res.statusCode = 500
      res.render '500'  # views/500.eco を使う

 そして、views/404.ecoとviews/500.ecoにそれぞれHTMLのテンプレートを用意しておきます。

 最後に、エラーページを表示したい箇所で次のようにエラーオブジェクトを引数にしてnext()を呼びます。その際、app.get()app.all()に渡すコールバック関数にnextという引数を追加します。

app.get '/404', (req, res, next) ->
  next new NotFound  # 404エラーをブラウザに返す

app.get '/500', (req, res, next) ->
  next new Error 'Server Error!'  # 500エラーをブラウザに返す

app.all '*', (req, res, next) ->
  next new NotFound  # 404エラーをブラウザに返す

開発用のエラーページ

 本番環境用に作られたエラーページではセキュリティやデザイン上の理由で技術的なエラーの詳細を表示しないことが多いため、開発時には不便に感じることがあります。そこで、本番環境では独自のエラーページを表示し、開発環境ではスタックトレースなどの技術的な詳細を含むエラーページを表示するようにしておくと便利です。

 環境ごとに異なるエラーページを表示するには、エラーページ表示用のミドルウェア部分を次のように書き換えます。

# 開発環境(通常通り起動した場合)
app.configure 'development', ->
  # スタックトレース付きで標準のエラーページを表示する
  app.use express.errorHandler showStack: true, dumpExceptions: true
  
# 本番環境(NODE_ENV=production の環境で起動した場合)
app.configure 'production', ->
  app.use (err, req, res, next) ->
    # 独自のエラーページを表示する
    if err instanceof NotFound  # NotFoundの場合
      res.statusCode = 404
      res.render '404'
    else  # それ以外のエラーの場合
      res.statusCode = 500
      res.render '500'

 このようにすると、src/app.coffeeを普通に起動した場合はdevelopment(開発環境)の方が実行されて標準のエラーページがスタックトレースとともに表示されます(例:図3)。一方、環境変数NODE_ENVにproductionという値をセットして起動した場合はproduction(本番環境)の方が実行されて独自のエラーページが表示されます(リスト26)。

[リスト26]開発環境と本番環境を使い分ける
# 開発環境で起動する
$ coffee src/app.coffee

# 本番環境で起動する
$ NODE_ENV=production coffee src/app.coffee
図3 標準のエラーページ
図3 標準のエラーページ

 

 次回も引き続き、CoffeeScriptでNode.jsアプリケーションを開発する際によく使われる実用的な開発手法を紹介します。

この記事は参考になりましたか?

  • X ポスト
  • このエントリーをはてなブックマークに追加
CoffeeScriptによるモダンなWebアプリケーション開発連載記事一覧

もっと読む

この記事の著者

飯塚 直(イイヅカ ナオ)

1984年東京都生まれ。 高校時代に趣味でPerlやJavaを使ってプログラミングを始める。 慶応大学湘南藤沢キャンパス卒業後、共同通信社にてニュースサイトの開発などを担当。 その後、面白法人カヤックにてソーシャルゲームの開発などを手がける。 2012年現在、カヤックを退社し個人として活動し...

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

  • X ポスト
  • このエントリーをはてなブックマークに追加
CodeZine(コードジン)
https://codezine.jp/article/detail/6528 2012/05/01 14:00

おすすめ

アクセスランキング

アクセスランキング

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー

アクセスランキング

アクセスランキング