はじめに
ドットインストールのExpress講座は最終更新日が2014年のままアーカイブされていて、講座内容そのままやってもうまくいかない箇所がある。
「» 19 記事を更新/削除してみよう」
の章では、Expressのmethod-overrideモジュールの仕様が変わっているため、POSTメソッドの書き換えができず、エラーが出てしまった。新しい仕様に対応した正しいやり方をメモしておく。
Expressの公式ドキュメントでは、Express3からExpress4にバージョンが変わって以降、仕様変更されたミドルウェア・システムがまとめられている。
「Express 4 への移行」
https://expressjs.com/ja/guide/migrating-4.html
※「method-overrideモジュール」とは
ブラウザを、HTTPのPUT,DELETEメソッドに対応させるためのモジュール。
Express では get、post、put、delete というHTTPリクエストを送れるが、ブラウザは get と post にしか対応していない。
「method-overrideモジュール」を読み込むと、ブラウザを put と delete に対応させることができる。
ドットインストールで作成するブログアプリのファイル構成
├── app.js
├── node_modules
├── package-lock.json
├── package.json
├── routes //ルーティング処理
│ └── post.js
└── views //テンプレートエンジン
├── partials //UIの共通パーツを別ファイル化
│ ├── footer.ejs
│ └── header.ejs
└── posts
├── edit.ejs //編集ページ
├── index.ejs //記事タイトル一覧ページ(編集・削除ボタン付き)
├── new.ejs
└── show.ejs
講座のままだとエラーになる箇所
・app.js
var express = require('express'),
app = express(),
post = require('./routes/post');
app.set('views', __dirname + '/views');
app.set('view engine', 'ejs');
// middleware
app.use(express.json());
app.use(express.urlencoded());
app.use(express.methodOverride()); //Express3仕様の書き方(エラーの原因)
app.use(express.logger('dev'));
app.use(app.router);
// routing
app.get('/', post.index);
app.get('/posts/:id([0-9]+)', post.show);
app.get('/posts/new', post.new);
app.post('/posts/create', post.create);
app.get('/posts/:id/edit', post.edit);
app.put('/posts/:id', post.update);
app.delete('/posts/:id', post.destroy);
app.listen(3000);
console.log("server starting...");
・edit.ejs【編集ページ】
<%- include ('../partials/header'); %>
<h1>Edit</h1>
//HTMLにはputメソッドがないため、postメソッドをサーバー側で変更する
<form method="post" action="/posts/<%= id %>">
<input type="text" name="title" value="<%= post.title %>">
<input type="text" name="body" value="<%= post.body %>">
//以下の一文を入れるとputメソッドとして認識されるはず(実際にはPOSTメソッドと認識されエラーに)
<input type="hidden" name="_method" value="put">
<input type="hidden" name="id" value="<%= id %>">
<input type="submit" value="更新">
</form>
<p><a href="/">Back to Top</a></p>
<%- include ('../partials/footer'); %>
・index.ejs【記事タイトル一覧&編集・削除ボタン】
<%- include ('../partials/header'); %>
<h1>Posts</h1>
<ul>
<% for (var i=0; i<posts.length; i++){ %>
<li>
<a href="/posts/<%= i %>"><%= posts[i].title %></a>
<a href="/posts/<%= i %>/edit">[Edit]</a>
//HTMLにはdeleteメソッドがないため、見せかけのコードで代用
<form method="post" action="/posts/<%= i %>">
//以下の一文を入れるとdeleteメソッドとして認識されるはず(実際にはPOSTメソッドと認識されエラーに)
<input type="hidden" name="_method" value="delete">
<input type="hidden" name="id" value="<%= i %>">
<input type="submit" value="削除">
</form>
</li>
<% } %>
</ul>
<p><a href="/posts/new">Add new</a></p>
<%- include ('../partials/footer'); %>
method overrideの変更点
『Express公式ドキュメント method-override』( http://expressjs.com/en/resources/middleware/method-override.html )
「クエリ値を使用して上書きする(override using a query value)」
を参照
クエリ文字列の値を使用してメソッドをオーバーライドするには、methodOverride 関数の文字列引数にクエリ文字列キーを指定します。呼び出しを行うには、そのクエリ文字列キーの値としてオーバーライドされたメソッドを持つ URL に POST リクエストを送信します。クエリ値を使用するこの方法は、レガシーなブラウザをサポートしつつも新しいメソッドを使用しようとしている場合、通常はプレーンなHTML <form>要素と一緒に使用されます。
・javascript(サーバーサイド)
var express = require('express')
var methodOverride = require('method-override')
var app = express()
// override with POST having ?_method=DELETE
app.use(methodOverride('_method'))
・HTML(フロントエンド)
<form method="POST" action="/resource?_method=DELETE">
<button type="submit">Delete resource</button>
</form>
Express4バージョンで実装
・app.js
const express = require('express');
const app = express();
//POSTで渡ってきたものをreq.resで取得するmiddlewareのためのモジュール (※bodyParserはExpress4では不要?未確認)
const bodyParser = require('body-parser');
const methodOverride = require('method-override');
//ルーティングを記述したファイルをモジュールとして読み込み
const post = require('./routes/post')
app.set('views', __dirname + '/views');
app.set('view engine', 'ejs');
//POSTで渡ってきたものをreq.resで取得するためのmiddleware
app.use(bodyParser.json())
app.use(bodyParser.urlencoded({extended:false}));
//methodoverrideの引値を設定
app.use(methodOverride('_method'))
//ルーティング
app.get('/',post.index);
app.get('/posts/:id([0-9]+)',post.show);
app.get('/posts/new',post.new);
app.post('/posts/create',parseForm, csrfProtection,post.create);
app.get('/posts/:id/edit',csrfProtection, post.edit);
app.put('/posts/:id/',post.update);
app.delete('/posts/:id/',post.destroy);
app.listen(3000, () => {
console.log('server is up on port 3000')
});
・edit.ejs【編集ページ】
<%- include ('../partials/header'); %>
<h1>Edit</h1>
//クエリ値を指定してHTTPメソッドを上書き
<form method="post" action="/posts/<%= id %>?_method=PUT">
<input type="text" name="title" value="<%= post.title %>">
<input type="text" name="body" value="<%= post.body %>">
<input type="hidden" name="id" value="<%= id %>">
<input type="submit" value="Update!">
</form>
<p><a href="/">Back to Top</a></p>
<%- include ('../partials/footer'); %>
・index.ejs【記事タイトル一覧&編集・削除ボタン】
<%- include ('../partials/header'); %>
<h1>Posts</h1>
<ul>
<% for (var i=0; i<posts.length; i++){ %>
<li>
<a href="/posts/<%= i %>"><%= posts[i].title %></a>
<a href="/posts/<%= i %>/edit">[Edit]</a>
//クエリ値を指定してHTTPメソッドを上書き
<form method="post" action="/posts/<%= i %>?_method=DELETE">
<input type="hidden" name="id" value="<%= i %>">
<input type="submit" value="削除">
</form>
</li>
<% } %>
</ul>
<p><a href="/posts/new">Add new</a></p>
<%- include ('../partials/footer'); %>