Express.jsには ルーティング機能があり、https://****.web.app/cards
や https://****.web.app/cards/sa01a_001
にアクセスした場合にそれぞれ指定した index.js が呼ばれる仕組みがある。
Expressのルーティング機能
app.js
(Cloud Functionsで動かす場合には index.js ) で、 /cards
にアクセスがあった場合に /routes/cards.js を呼び出してください といったもので、実際に /routes/cards.js
でレスポンスを返すことになる。
const cardRouter = require('./routes/cards'); const app = express(); //... app.use('/cards', cardRouter);
開発しているWebサイトが、ホームページとカードページだけであれば、まとめてレスポンスを返す処理を書いてもよいかもしれないが、Expressでは適切な単位でルーティング処理ができるように工夫されている。(たぶん)
express.Router でモジュール式のルートハンドラーの実装
下記のハンドラーではすべて res.render()
を返している。これは /cards
または /cards/sa01a_001
にアクセスされた時に index.pug
をレンダリングエンジンに渡します。レンダリングエンジンは HTMLを作ってブラウザに返します。
var express = require('express'); var router = express.Router(); /* GET cards page. */ router.get('/', function(req, res, next) { res.render('index', { title: 'カード一覧', message: 'テスト' }); }); /* GET card detail page. */ router.get('/sa01a_001', function(req, res, next) { res.render('index', { title: 'カード詳細', message: 'sa01a_001 のデータ' }); }); router.get('/sa01a_002', function(req, res, next) { res.render('index', { title: 'カード詳細', message: 'sa01a_002 のデータ' }); }); module.exports = router;
sa01a_001、sa01a_002、sa01a_003……とカードが増える度にハンドラーを追加していけばよい。しかし、実際にはカードデータは無数に増えることが想定されるので、手書きでハンドラーを増やすのは不可能である。
Expressでパスパラメータを取得する
パスパラメータとは、URLの https://****.web.app/cards/sa01a_001
での/sa01a_001
に相当する。ここでは任意のカードIDを取得してみよう。
router.get('/:cardId', function(req, res, next) { const cardId = `${req.params.cardId}`; //... });
次に取得した :cardId
を使って、Firestoreからカードデータを取得する。
var express = require('express'); var router = express.Router(); const firebaseAdmin = require('firebase-admin'); /* GET cards page. */ router.get('/', function(req, res, next) { res.render('index', { title: 'カード一覧', message: 'テスト' }); }); /* GET card detail page. */ router.get('/:cardId', function(req, res, next) { const cardId = `${req.params.cardId}`; // Firestore からカードデータを取得する const db = firebaseAdmin.firestore(); const docRef = db.collection('cards').doc(cardId); docRef.get().then(function(doc) { const data = doc.data(); res.render('index', { title: 'カード詳細', message: `${data.id}: ${data.name}` }); }).catch(function(error) { throw error; }); }); module.exports = router;
これでいくらでもカードが増えても対応が可能になりました。パラメータを変更すると良い感じにデータが表示されます。
存在しないデータにアクセスする場合
ユーザーが不適切なカードIDを指定した場合にどうなるのか? たとえば /cards/sa01a_001
ではなく /cards/hogehgoebooo
みたいにカードIDが指定された場合だ。
next(error);
でエラーを返すと、より上位のハンドラーで内容に合ったエラー処理をしてくれる。
//...省略 /* GET card detail page. */ router.get('/:cardId', function(req, res, next) { const cardId = `${req.params.cardId}`; // Firestore からカードデータを取得する const db = firebaseAdmin.firestore(); const docRef = db.collection('cards').doc(cardId); docRef.get().then(function(doc) { const data = doc.data(); res.render('index', { title: 'カード詳細', message: `${data.id}: ${data.name}: ${data.price}円` }); }).catch(function(error) { // 見つからなかったのでデータを 404 で返す error.status = 404 next(error); }); }); //...省略
express-generatorで雛形を作った場合 app.js
では、下記のようなハンドラーがあらかじめ用意されているので、
// error handler app.use(function(err, req, res, next) { // set locals, only providing error in development res.locals.message = err.message; res.locals.error = req.app.get('env') === 'development' ? err : {}; // render the error page res.status(err.status || 500); res.render('error'); });
僕は 404ページは専用のものを用意しておきたかったので、https://****.web.app/404
にリダイレクトされるようにした。
// error handler app.use(function(err, req, res, next) { // set locals, only providing error in development res.locals.message = err.message; res.locals.error = req.app.get('env') === 'development' ? err : {}; if (err.status == 404) { res.redirect('/404'); return; } // render the error page res.status(err.status || 500); res.render('error'); });
実行環境
- Node.js v12.14.0
- npm 6.14.4
- firebase-admin 8.12.0