【Next.js】Sequelizeでページング処理を実装する方法

4 min読了の目安(約3600字TECH技術記事

Next.jsとsequelizeを使ってWEBアプリを作っているとページネーションを実装することが多いと思います。

前回は、Reactのフロントエンドのみの処理を紹介しました。なので今回は、サーバーサイドでWeb APIとDBを使った方法を紹介します。

利用するライブラリーは以下の通りです。

  • Next.js(Web API)
  • sequelize(DB)
  • material-ui(ページネーション)

完成イメージ

サーバーサイドのデータ取得

まずはサーバーサイドの処理を紹介します。http://localhost:3000/api/book?page=「ページ番号」にアクセスすると

  • 5件に絞られた本のタイトル
  • 総ページ数
    この2つを取得するAPIを作ります。総ページ数はページネーションの一番右側の最終ページを表示するためのものです。
import book from "@model/bookModel"

export default async (req, res) => {
    const PAGE_NUM = 5;    //1ページに表示する件数
    const offset_coefficient = !req.query || !req.query.page ? 0: req.query.page - 1;  //ページ番号
    
    //ユーザーに登録されている本を全て取得
    const book_list = await book.findAndCountAll({
        order: [
            ['book_id', 'ASC']              //作成日時でソート
        ],
        limit: PAGE_NUM,                       //1ページ毎の件数
        offset: PAGE_NUM * offset_coefficient  //飛ばす件数
    });

    //総ページ数
    book_list["count"] = Math.ceil(book_list["count"] / PAGE_NUM)

    res.statusCode = 200
    res.setHeader('Content-Type', 'application/json')
    res.end(JSON.stringify(book_list));
}

まずは、req.query.pageを取得して1を引いた値をoffset_coefficient に入れておきます。この値が開始位置を設定する係数になります。

そして、SequelizeのfindAndountAllで対象のデータと総件数を取得します。limitで1ページ当たりの件数、offsetに先ほど取得したoffset_coefficient から計算した値をセットして5件のみ取得します。

findAndCountAllで取得した値は以下のパラメータに入っています。

  • row:取得データ
  • count:総件数

このcountはリミットで絞った件数ではなくテーブルに入っているデータの件数になります。そのためcountを1ページ当たりの件数5で割り、Math#ceilで切り上げを行うことで総ページ数を計算しているのです。

後は、取得したデータと総ページ数をレスポンスにセットして返すだけです。

クライアントサイドのページネーション設定

クライアントでは初期表示時とページ番号をクリックしたときにWeb APIにアクセスしてデータを取得して表示しています。

import MuiPagination from '@material-ui/lab/Pagination';
import { withStyles } from '@material-ui/core/styles';
import {useEffect, useState} from 'react'

export default function Index() {
  const [page, setPage] = useState(1);            //ページ番号
  const [count, setCount] = useState();           //総ページ数
  const [bookList, setBookList] = useState([]);   //取得した本のリスト

  //初回のみ実行
  useEffect(async () => {
    setBookListAPI(page);
  }, []);

  //ページ番号をクリックしたときの処理
  const clickPage = (e, page) => {
    setPage(page);
    setBookListAPI(page);
  }

  //取得データのセットと総データ件数をセットする
  const setBookListAPI = async (page) => {
    const response = await fetch(`http://localhost:3000/api/book?page=${page}`);
    const data = await response.json();
    setBookList(data.rows);    //取得データ
    setCount(data.count);      //総データ件数
  }

  const Pagination = withStyles({
    root: {
      display: 'inline-block',  //中央寄せのためインラインブロックに変更
    },
  }) (MuiPagination);

  return (
    <>
      <ul>
        {bookList.map((book) => <li>{book.book_title}</li>)}
      </ul>
      
      <div style={{marginTop: "50px", textAlign: "center"}}>
        <Pagination 
          count={count}         //総ページ数
          color="primary"       //ページネーションの色
          onChange={clickPage}  //変更されたときに走る関数。第2引数にページ番号が入る
          page={page}           //現在のページ番号
        />
      </div>
    </>
  );
}

useEffectの第2引数に空配列を指定することで初回のみsetBookListAPIが実行されるようになっています。そして、setBookListAPI内では、APIからデータを取得して本のデータをbookList、総ページ数をcountのステータスに設定。bookListは本のタイトルをリスト表示し、ページネーションに総ページ数countを渡して最終ページ番号を表示しています。

次にページ番号が押されたときはonChangeメソッドでclickPageを呼び出し、ページ番号をセットしています。後は初期表示時と同じでsetBookListAPIを呼び出して本のデータを取得しています。

material-uiのページネーションについては前回の記事で紹介しているので、こちらを参考にしてください。

まとめ

  • findAndCountAllを使うことで総件数と対象のデータを取得できる
  • クライアント側では初回表示時とページ番号が変更されたときにWeb APIを呼び出して表示データを更新する