Webアプリケーションの開発
Webアプリケーションを開発する時、すべてを一から開発するのは大変です。幸いなことにNode.jsにはExpress(MITライセンス)というWebフレームワークがあり、これを土台として楽にWebアプリケーションを開発できます。なお、本稿は執筆時点での最新バージョンであるExpress 3.0に基づいています。
Expressを使うと、たった5行のソースコードで最小限のWebサーバを作ることができます(リスト18)。
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に示します。もしデータベースのパスワードなどを書いた場合、誤って公開してしまうことがないように気をつけてください。
module.exports = environment: 'development' mysql: host: '127.0.0.1' port: 3306 database: 'dbname' username: 'dbuser' password: 'dbpassword'
Webサーバのメインプログラムをsrc/app.coffeeとし、リスト20の内容で作成します。
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)。
<!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のように記述します。パラメータとして受け取る箇所を「:パラメータ名」で指定します。
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のように記述します。
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)。
res.render 'index.eco', name: "矢野太郎" email: "taro@example.com"
<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)。
# 開発環境で起動する $ coffee src/app.coffee # 本番環境で起動する $ NODE_ENV=production coffee src/app.coffee
次回も引き続き、CoffeeScriptでNode.jsアプリケーションを開発する際によく使われる実用的な開発手法を紹介します。