酢ろぐ!

カレーが嫌いなスマートフォンアプリプログラマのブログ。

Node.jsで Expressのパスパラメータを取得してFirestoreのデータを表示する

Express.jsには ルーティング機能があり、https://****.web.app/cardshttps://****.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;

これでいくらでもカードが増えても対応が可能になりました。パラメータを変更すると良い感じにデータが表示されます。

f:id:ch3cooh393:20200511121028p:plainf:id:ch3cooh393:20200511121032p:plain

存在しないデータにアクセスする場合

ユーザーが不適切なカード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