Node.js + Express で 検索画面 を作る

0 件のコメント

Node.js + Express + MongoDB を使った「検索」機能のサンプルを作ってみます。 今回はサンプルとして「店舗検索」を行うような機能を実装してみます。

概要

店舗検索として、検索条件入力で入力した条件にマッチする店舗一覧が表示されるような機能を作ります。 検索結果画面には検索結果総数、ページネーション(ページング)を実装します。

以下、基本設計…とまではいきませんが、本記事で作ろうとしている機能の概要をまとめます。

画面

「検索条件入力」画面と「検索結果出力」画面の2画面を作成します。 以下には画面ごとに作成する項目もあわせて記載しています。

  • 検索条件入力 (/search/index.ejs)

    • 検索テキスト
    • 検索ボタン
  • 検索結果出力 (/search/result-list.ejs)

    • 検索テキスト
    • 検索ボタン
    • 検索結果総数
    • 検索結果リスト
      • 店舗名
      • 店舗所在地
      • 店舗電話番号
    • ページネーション

データベース

以下のようなドキュメントスキーマを想定します。

database
test
collection
shops
document
shop = {
  name:     { type: string },
  location: { type: string },
  tel:      { type: string }
}

何はともあれ試すにはデータが必要なので、以下のコードをコピペして MongoDB に追加しておきます。

db.shops.insert({name: "abc shop1", location: "shibuya", tel: "0120-111-1111"});
db.shops.insert({name: "bcd shop2", location: "shinagawa", tel: "0120-111-2222"});
db.shops.insert({name: "cde shop3", location: "shinjuku", tel: "0120-111-3333"});
db.shops.insert({name: "def shop4", location: "ikebukuro", tel: "0120-111-4444"});
db.shops.insert({name: "efg shop5", location: "akihabara", tel: "0120-111-5555"});

実装

それでは、Node.js + Express + MongoDB を使って実装を行っていきます。 作成するファイルおよび実装は以下の通りです。 ファイルごとに実装の内容を見ていきます。

/app.js

var express = require("express");
var bodyParser = require("body-parser");
var MongoClient = require("mongodb").MongoClient;

// expressインスタンス生成
var app = express();

// テンプレートエンジンの設定
app.set("views", "./views");
app.set("view engine", "ejs");

// ミドルウエアの設定
app.use("/public", express.static("public"));
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());

// ルーティング設定
app.use("/shop", (function () {
  var router = express.Router();

  // GET: /shop/search
  router.get("/search", function (request, response) {
    var URL = "mongodb://localhost:27017/test";
    var MAX_ITEMS_PER_PAGE = 1;
    var query = request.query.q;
    var page =request.query.pg ? parseInt(request.query.pg) : 1;

    // 検索クエリがない場合、初期表示
    if (!query) {
      return response.render("./search/index.ejs");
    }

    // 検索クエリがある場合、データベースを検索して結果リストを表示
    MongoClient.connect(URL).then((db) => {
      return Promise.all([
        // 検索総ヒット数
        db.collection("shops").find({
          name: new RegExp(`.*${query}.*`)
        }).count(),
        // 現在ページに表示する内容
        db.collection("shops").find({
          name: new RegExp(`.*${query}.*`)
        }).skip((page - 1) * MAX_ITEMS_PER_PAGE).limit(MAX_ITEMS_PER_PAGE).toArray()
      ]);
    }).then((results) => {
      // ビューへ渡すデータを整形
      var data = {
        count: results[0],
        list: results[1],
        pagination: {
          max: Math.ceil(results[0] / MAX_ITEMS_PER_PAGE),
          current: page,
          isFirst: page === 1,
          isLast: page === Math.ceil(results[0] / MAX_ITEMS_PER_PAGE)
        },
        query: query
      };
      // ビューを表示
      return response.render("./search/result-list.ejs", data);
    }).catch((reason) => {
      // エラー処理
      console.log(reason);
    });

  return router;
})());

// サーバー起動
app.listen(3000);

メインとなる処理です。 Node.js、Express、MongoDB の基本的な処理についてはここでは割愛します。

L.29-31: 検索クエリの有無はクエリパラメーター q に値が設定されているかどうかで判定しています。 値が設定されていなければ初期表示を行います。 全件策したいところですが…データが多くなった時に応答時間が長くなりそうなので止めておきます。。

L35-44: データベースから取得する情報は「検索結果総数」と「現在ページに表示する内容」の2種類です。 それぞれのデータは非同期で取得するので Promise.all() を使ってすべての処理が完了するのを保証します。

L47-57: ビューへ渡すデータを整形します。 ページネーション(ページング)に必要な情報はここで整形しておきます。 ページネーションを作るにはループで作成するので、「ページ総数 (max)」と「現在ページ番号 (current)」は最低でも準備しておきます。

/views/search/index.ejs

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>search</title>
  </head>
  <body>
    <h1>検索</h1>
    <form action="/shop/search" method="GET">
      <input type="text" id="q" name="q" />
      <input type="submit" value="検索" />
    </form>
  </body>
</html>

検索条件入力画面の初期表示ページ。 結果はないので検索条件を入力するだけの単純なページです。 検索条件クエリ―の有無で表示ページを出しわけしているので、フォームのメソッドは GET としています。

/views/search/result-list.ejs

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>search</title>
    <style>
      .result-item { border: 1px solid #666; margin: 0.5em 1em; }
      .pagination { list-style: none; display: flex; }
      .pagination li { display: inline-block; width: 2em; }
    </style>
  </head>
  <body>
    <h1>検索</h1>
    <form action="/shop/search" method="GET">
      <input type="text" id="q" name="q" value="<%= query %>"/>
      <input type="submit" value="検索" />
    </form>
    <p>結果:<%= count %>件</p>
    <p>
<% for(var i = 0; i < list.length; i++) {%>
<%- include("result-item.ejs", list[i]); %>
<% }%>
    </p>
    <p>
      <ul class="pagination">
<% for (var i = 1; i <= pagination.max; i++) {%>
<%   if (i !== pagination.current) {%>
        <li><a href="/shop/search?q=<%= query %>&pg=<%= i %>"><%= i %></a></li>
<%   } else {%>
        <li><%= i %></li>
<% }}%>
      </ul>
    </p>
  </body>
</html>

検索結果表示画面。 リストの外枠だけを記述して個別検索結果は include で読み込むようにします。

L18: 検索結果総数の表示は count に保持しているのでそれを表示します。

L20-22: 検索結果リストを for でループしながら include に値を入れて表示を行います。 具体的な表示内容はこの後の result-item.ejs を確認してください。 include を利用するときは <%- include("VIEW_FILEPATH") %> と記述する点に注意です。

L26-31: ページネーション(ページング)の表示です。 表示する「ページ総数」と「現在ページ」はサーバー側 (app.js) で算出しているので、それらを利用して表示を行います。

/views/search/result-item.ejs

<div>
  <table class="result-item">
    <tr>
      <th>name</th>
      <td><%= name%></td>
    </tr>
    <tr>
      <th>location</th>
      <td><%= location%></td>
    </tr>
    <tr>
      <th>tel</th>
      <td><%= tel%></td>
    </tr>
  </table>
</div>

検索結果の各項目を定義しています。 MongoDB から取得した値がそのまま流れてきているので、表示したいデータを埋め込んでいきます。

テスト

上記の実装がすべて終われば動作するはずなので、動作確認を行います。

  • 検索条件入力

  • 検索結果出力

スタイルの実装を最低限としたので、最初の 概要 のイメージとは少しデザインが異なりますが…機能としては必要なものが盛り込まれているかと思います。

今回の記事は参考になったでしょうか? 一般的な検索ページの作成をテーマに Node.js + Express でどのように実装するかをまとめてみました。 本記事(検索ページ作成)のポイントは以下の通りです。

  • 検索リクエストは GET
  • 検索結果画面には「検索入力」、「検索結果総数」、「検索結果」、「ページネーション」を表示
  • MongoDB への複数リクエストは Promise.all() で非同期ですべて取得
  • 「ページネーション」に必要な値はサーバー側で準備