Next.js と Google Books APIs で簡単な書籍検索アプリを作ってみよう

2022年2月7日月曜日

Next.js

t f B! P L

Next.js と Google Books APIs を使って、簡単な書籍検索アプリを作っていきたいと思います。

完成イメージ

今回作成する書籍検索アプリの完成イメージは、次のとおりです。

enter image description here

Next.js とは

Next.js は Reactをベースにしたフロントエンドフレームワークです。ゼロコンフィグで使えるファイルベースルーティングや、表示速度を高速化するサーバーサイドレンダリングなど、規模が大きい React アプリを作る時に便利な機能が詰まっています。

さらに、Next.js にはバックエンド側の機能も備わっているため、フロントエンドと同じプロジェクトで Node.js を使ってバックエンド側の処理を作ることもできます。

Google Books APIs とは

Google Books APIs は、その名のとおり Google が提供している書籍検索 API のひとつです。登録や、API キーのなどの認証が必要なく、プログラミングを学習する時などに便利なライブラリです。

https://developers.google.com/books

プロジェクトの作成

適当なプロジェクト名で、Next.js のプロジェクトを作成します。(今回は「sample-books」という名前でプロジェクトを作成しました)

また、言語は TypeScirpt を使用します。

npx create-react-app sample-books --template typescript

バックエンド側の API の作成

まずは、バックエンド側の処理を作ります。

いきなりフロント側から Google Books APIs にアクセスしてもいいですが、アプリで使わない JSON のタグも多く、それのせいでフロント側の処理が煩雑になるため、バックエンド側で API を実行し、必要な情報のみに絞った JSON データに整形し、それをフロント側に渡すようにします。

pages/api/ の下に books.ts を作成し、 Google Books APIs からデータを取得し、JSONデータを整形後、フロントに返す処理を書きます。

import type { NextApiRequest, NextApiResponse } from 'next'

// google books api から取得した情報の簡易版インタフェース
export interface Book {
  id: string,
  title: string,
  description: string,
  pageCount: number | null,
  image: string,
  mainCategory: string,
  categories: string[],
}

/**
 * Google Books API で鬼滅関連の書籍を検索する関数
 * [GET]/api/books
 * 
 * @param req リクエスト
 * @param res レスポンス
 */
 export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse<Book[]>
) {
  let q = req.query.q || ""
  q = Array.isArray(q) ? q[0] : q
  const data = await getData(q)
  res.status(200).json(data)
}

/**
 * Google Books API の実行と取得結果(JSON)の整形を行う関数
 *
 * @param q 検索クエリ
 * @returns 見つかった書籍のリスト
 */
export async function getData(query: string) : Promise<Book[]> {
  const response = await fetch("https://www.googleapis.com/books/v1/volumes?q=" + encodeURIComponent(query))
  const jsonData = await response.json()
  return jsonData.items.map((elem: any) => {
    return {
      id: elem.id,
      title: elem.volumeInfo.title,
      description: elem.volumeInfo?.description,
      pageCount: elem?.pageCount,
      image: elem.volumeInfo?.imageLinks?.thumbnail,
      mainCategory: elem.volumeInfo?.mainCategory,
      categories: elem.volumeInfo?.categories,
    }
  })
}

Google Books APIs から返ってくる JSON データの詳細は、公式の次のページを見てください。
Google Books APIs Reference volumes

フロント側の作成

書籍検索ページの作成

pages/ の下に sample.tsx を作成し、検索ボックスと、検索結果を表示するリストを作る。また、onClickSearch 関数では、検索ボタンがクリックされた時に、上で作成したバックエンド側の API を呼び出している。

import BookItem from '../components/BookItem';
import type { NextPage } from 'next';
import { SyntheticEvent, useEffect, useState } from 'react';
import styles from '../styles/Sample.module.css';
import { Book } from './api/books';

const Sample: NextPage = () => {

  const [query, setQuery] = useState("");                 // 検索条件
  const [items, setItems] = useState(new Array<Book>());  // 書籍リスト

  /**
   * 検索ボタンをクリックした時の処理
   * サーバーサイドのAPIを呼び出し、書籍情報の検索を行う
   */
  const onClickSearch = async (e: SyntheticEvent) => {
    const response = await fetch("/api/books?q=" + encodeURIComponent(query))
    setItems(await response.json());
  };

  // Render
  return (
    <div className={styles.container}>
      <input type="text" value={query} onChange={e => setQuery(e.target.value)}></input>
      <button onClick={onClickSearch}>検索</button>
      <ul className={styles.books}>
        {items.map((item, index) => (
          <li key={index}>
            <BookItem book={item}></BookItem>
          </li>
        ))}
      </ul>
    </div>
  )
}

export default Sample

BookItem コンポーネントの作成

components/BookItem の下に BookItem.tsx というファイルを作成する。

import { Book } from 'pages/api/books';
import styles from './BookItem.module.css'

type AppProps = { book: Book };
const BookItem = ({book}: AppProps) => {

  return (
    <div className={styles.book_grid_wrapper}>
      <>
        {book.image ?
          <img className={styles.book_grid_image} src={book.image}/> :
          <div>No Image</div>
        }
      </>
      <div>
        <div className={styles.book_grid_title}>{book.title}</div>
        <div>
          {(book.categories || []).map((tab) => {
            <div>{tab}</div>
          })}
        </div>
      </div>
      <div className={styles.book_grid_description}>
        <div className={styles.inner}>
        {book.description}
        </div>
      </div>
    </div>
  )
}

export default BookItem;

同フォルダに BookItem.module.css も作成し、スタイルを指定する。

.book_grid_wrapper {
  display: grid;
  grid-template-columns: 200px 2fr 1fr;
  grid-auto-rows: minmax(5rem, auto);
}

.book_grid_image {
  align-self: start;
}

.book_grid_title {
  align-self: start;
}

.book_grid_description {
  align-self: start;
}

.book_grid_description .inner {
  display: -webkit-box;
  overflow: hidden;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
}

まとめ

かなりザックリな説明になったが、これで一通りの実装は完成である。実行して動きを確かめてみてください。

スポンサーリンク
スポンサーリンク

このブログを検索

Profile

自分の写真
Webアプリエンジニア。 日々新しい技術を追い求めてブログでアウトプットしています。
プロフィール画像は、猫村ゆゆこ様に書いてもらいました。

仕事募集もしていたり、していなかったり。

QooQ