Express.jsとMongoDBでページネーションする

![Kobito.dU2yiW.png](https://qiita-image-store.s3.amazonaws.com/0/81715/4ddd327b-bbdd-aaf2-24ac-b4f735f2ff64.png "Kobito.dU2yiW.png")

## 環境
* Node.js: v4.3.2
* MongoDB: 3.2
* Express.js: 4.13.1
* Bootstrap: 3.3.6（ビューで使用）

## 準備
`express-generator`でひな形を作成し、追加のパッケージをインストールしておきます。MongoDBへのアクセスには`mongoose`を使用します。

Expressでページネーションを行うために、`express-paginate`というパッケージを使用します。また、`mongoose-paginate`をあわせて使用することで楽にページネーション出来るようになります。

```
$ express pagination-test

$ cd pagination-test

$ npm i express-paginate mongoose mongoose-paginate --save
```

## モデルの作成
`name`というプロパティのみを持つ`User`モデルを作成します。

```user.js
var mongoose = require('mongoose');
var mongoosePaginate = require('mongoose-paginate');
var Schema = mongoose.Schema;

var userSchema = new Schema({
  name: String
});

userSchema.plugin(mongoosePaginate);

var mongoose = require('mongoose');
mongoose.connect('mongodb://localhost:27017/test');

var User = mongoose.model('User', userSchema);
module.exports = User;
```

`mongoose-paginate`はプラグインとして機能します。ユーザスキーマにこのプラグインを追加しています。

MongoDBにはテストデータを入れておきましょう。ページネーションのテストをするのに、ユーザデータが１００件くらいあるといいです。[このサイト](https://www.mockaroo.com/)などでテストデータを作成すると楽です。

## ミドルウェアの追加
アプリケーションにページネーションのためのミドルウェアを追加します。

```app.js
...
// この２行を追加します。
var paginate = require('express-paginate');
app.use(paginate.middleware(10, 50));

// ルーティングの前に追加します。
app.use('/', routes);
app.use('/users', users);
...
```

ミドルウェアの書式は以下のようになっています。

```
paginate.middleware(limit, maxLimit)
```

* `limit`: １ページに含めるドキュメント（アイテム）数のデフォルト値
* `maxLimit`: アプリケーションのクライアントが指定できる`limit`の最大値

もう少しわかりやすく説明すると、ブラウザで以下のように指定したとします。

```
http://localhost/?page=1&limit=10
```

`limit=10`は、表示されるユーザ数です。`limit`の指定がなかった場合、上記のデフォルト値（ここでは１０）になります。

また、`maxLimit`が５０に設定されていた場合、ブラウザで`limit=100`と指定しても、表示されるのは５０ユーザまでです。

## コントローラ
以下のようにします。

```routes/index.js
var express = require('express');
var router = express.Router();
var User = require('../user');
var paginate = require('express-paginate');

router.get('/', function(req, res, next) {
  User.paginate({}, {page: req.query.page, limit: req.query.limit}, function(err, result) {
    res.render('index', {
      users: result.docs,
      currentPage: result.page,
      pageCount: result.pages,
      pages: paginate.getArrayPages(req)(3, result.pages, req.query.page)
    });
  });
});

module.exports = router;
```

`mongoose-paginate`プラグインにより、`User`モデルはpaginateメソッドを使用できます。第一引数にクエリ、第二引数にはページネーションのためのオプションを渡しています。

ブラウザで以下のように指定したとすると、

```
http://localhost/?page=2&limit=10
```

MongoDBから１１〜２０番目のユーザを取得します。

コールバック関数に渡される`result`には、取得したドキュメント以外の情報も格納されています。

* `result.docs`: 取得したドキュメントの配列
* `result.page`: 指定したページ番号
* `result.pages`: 総ページ数

他にも`result.total`（総ドキュメント数）などがあります。詳しくは[Githubページ](https://github.com/edwardhotchkiss/mongoose-paginate)へ。

`paginate.getArrayPages`は、各ページのURLとページ番号を格納したオブジェクト配列を返します。ビューでページネーションボタンを作成するのに使います。

## ビュー
最後にビューの設定を行います。ページネーションボタンの作成に、Bootstrapを使用しています。

```views/index.jade
extends layout

block content
  h1 Users

  ul.list-unstyled
    each user in users
      li= user.name

  if paginate.hasPreviousPages || paginate.hasNextPages(pageCount)
      ul.pagination
        if paginate.hasPreviousPages
          li
            a(href=paginate.href(true))
              span &laquo;
        if pages
          each page in pages
            li(class=(currentPage === page.number) ? 'active' : '')
              a(href=page.url)= page.number
        if paginate.hasNextPages(pageCount)
          li
            a(href=paginate.href())
              span &raquo;

```

ビューでは`paginate.hasPreviousPages`と`paginate.hasNextPages`が使用できます。それぞれ、前のページ、後のページが存在するとき、`true`を返します。

`paginate.href(true)`は、前のページを表示するURLを返します。例えば、現在表示しているのが

```
http://localhost/?page=2&limit=10
```

であったとき、`paginate.href(true)`は

```
http://localhost/?page=1&limit=10
```

を返します。`paginate.href()`は後のページを表示するURLを返します。

`page.url`および`page.number`は、それぞれのページのURLとページ番号です。ページ番号のリンクをクリックすると、そのページの画面を表示するというページネーションボタンを簡単に作成出来るようになっています。