理系公務員のプログラミング日記

【Laravel9×Vue3】本の貸し出しシステム9(楽天APIとの連携)

タグ:
Laravel
Vue

アプリを充実させる

「書籍管理サービス」としてだいぶ機能が揃ってきました。(たぶん)

ここからは、今まで作った機能を充実させていきたいと思います。

楽天BooksAPIとの連携

管理する書籍を追加する機能をBookAdd.vueで実装しました。

これを運用することを考えると、書籍のタイトルや著者名、ISBN番号をユーザーに手入力させるのはかなりの手間になります。

楽天APIから書籍のデータを一式で取得し、これをデータベースに保存する機能を追加したいと思います。

まず基本となる楽天APIからデータを取得する方法は下記を参照してください。

Vue3で非同期処理を利用して楽天APIから本のデータを取得する

楽天BooksAPIのオブジェクトの形と、既存のBookモデルの形を比較する

データを取り込むためには、APIの形と既存のBookモデルの形を比較する必要があります。

楽天ブックス書籍検索APIに title="Java"のクエリを追加した場合の最初の1件

{ "GenreInformation": [], "Items": [ { "Item": { "affiliateUrl": "", "author": "中山清喬/国本大悟", "authorKana": "ナカヤマ,キヨタカ/クニモト,ダイゴ", "availability": "1", "booksGenreId": "001005003007/001005005001/001005005009/001005017", "chirayomiUrl": "", "contents": "", "discountPrice": 0, "discountRate": 0, "isbn": "9784295007807", "itemCaption": "圧倒的人気No.1入門書の増補改訂版!コレクションを追加!基本文法やオブジェクト指向の「なぜ?」が必ずわかる!", "itemPrice": 2860, "itemUrl": "https://books.rakuten.co.jp/rb/16099007/", "largeImageUrl": "https://thumbnail.image.rakuten.co.jp/@0_mall/book/cabinet/7807/9784295007807.jpg?_ex=200x200", "limitedFlag": 0, "listPrice": 0, "mediumImageUrl": "https://thumbnail.image.rakuten.co.jp/@0_mall/book/cabinet/7807/9784295007807.jpg?_ex=120x120", "postageFlag": 2, "publisherName": "インプレス", "reviewAverage": "4.5", "reviewCount": 20, "salesDate": "2019年11月", "seriesName": "", "seriesNameKana": "", "size": "単行本", "smallImageUrl": "https://thumbnail.image.rakuten.co.jp/@0_mall/book/cabinet/7807/9784295007807.jpg?_ex=64x64", "subTitle": "", "subTitleKana": "", "title": "スッキリわかるJava入門第3版", "titleKana": "スッキリ ワカル ジャバ ニュウモン" } }, // 略

BooksTableSeederの最初の1件

$param = [ 'title' => 'スッキリわかるJava入門 第3版', 'author' => '中山 清喬', 'publisher' => 'インプレス', 'ISBN' => '978-4295007807', 'summary' => '発売から8年であっという間に総計40万部到達した大人気シリーズの原点。 「どうして? 」「なぜそうなる? 」が必ずわかるJava入門書史上最強の定番書! 読みやすさ、使いやすさをさらに磨いた増補改訂版登場!', 'gunre' => 'Java', 'available' => true, ]; DB::table('books')->insert($param);

楽天APIのかたちに合わせて、Bookモデルを修正します。

ついでに画像も表示したいので、画像のURLが入っているlargeImageUrlを追加します。

Bookモデルの修正

protected $fillable = [ 'title', 'author', // 'publisher', 'publisherName', // 'ISBN', 'isbn', // 'summary', 'itemCaption', 'gunre', 'largeImageUrl', 'available', ];

データを取り込む処理を実装する

楽天APIからデータを取得する処理を実装します。方法としては、フロントエンド側からaxiosを使ってAPIを叩く方法と、バックエンド側からguzzleというPHPのHTTPクライアントを使ってAPIを叩く方法があります。

今回はバックエンド側からAPIを叩く方法を採用します。

Guzzleについて

GuzzleはPHPのHTTPクライアントです。HTTPリクエストを送信することができます。

PHPでAPIを叩く場合、PHPの標準ライブラリであるcurlを使ってHTTPリクエストを送信することができます。しかし、curlは複雑なHTTPリクエストを送信するのに向いていません。Guzzleは、curlをラップして、より簡単にHTTPリクエストを送信することができます。

PHP環境の場合はcomposerを使ってインストールする必要がありますか、Laravelの今回の環境ではデフォルトでインストールされています。

composer.jsonを確認してください。

composer.json

"require": { "php": "^8.0.2", "guzzlehttp/guzzle": "^7.2", "laravel/framework": "^9.19", "laravel/sanctum": "^3.0", "laravel/tinker": "^2.7", "laravel/ui": "^4.2" }, // 略

このguzzleを利用して、楽天APIを叩いてみましょう。

BookControllerに楽天APIを叩く処理を追加する

BookController.php

namespace App\Http\Controllers; use Illuminate\Http\Request; use App\Models\Book; use LDAP\Result; // 追加 use GuzzleHttp\Client; class BookController extends Controller { // 略 public function getRakutenAPI(Request $request) { $title = $request->title; $client = new Client(); $res = $client->request('GET', 'https://app.rakuten.co.jp/services/api/BooksBook/Search/20170404', [ 'query' => [ 'format' => 'json', 'applicationId' => env('RAKUTEN_APP_ID'), 'title' => $title, ] ]); $body = $res->getBody(); $json = json_decode($body, true); return $json; } }

アプリケーションIDは、githubなどで外部に公開することは望ましくないので環境変数に設定します。

.envファイルに楽天APIのアプリIDを追加する

RAKUTEN_APP_ID=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

最後にapi.phpにルーティングを追加します。

api.php

// 上に書く Route::get('/books/getRakutenAPI', [BookController::class, 'getRakutenAPI']); Route::apiResource('/books',BookController::class);

最初に作ったapiResourceが作るルーティングには、ワイルドカードを表すbooks/{book}というルーティングが入っています。

今回の楽天APIを叩くルーティングは、books/{book}よりも上に書かないと、books/{book}のルーティングに引っかかってしまいます。

フロントエンド側で楽天APIを叩く処理を実装する

BookAdd.vue

<template> // 略 <div class="form-group"> <label for="title">タイトル</label> <input type="text" class="form-control" id="title" v-model="title"> <button class="btn btn-primary" v-on:click="getRakutenAPI">検索</button> </div> <div v-for="book in books"> <div class="card" style="width: 18rem;"> <img v-bind:src="book.Item.largeImageUrl" alt=""> <p>{{ book.Item.title }}</p> </div> </div> </template> <script> data() { return { // 略 title: '', books: [], } }, methods: { // 略 async getRakutenAPI() { const res = await axios.get('/api/books/getRakutenAPI', { params: { title: this.title } }); this.books = res.data.Items; }, }, </script>

これで楽天APIを叩いて、データを取得することができました。

次に登録したいデータを選んで、登録する処理を実装していきます。

データを登録する処理を実装する

v-onclickでaddBookメソッドを呼び出すようにします。

このときにaddBookメソッドに引数を渡して、その引数を使ってデータを登録します。

BookAdd.vue

<template> // 略 <div v-for="book in books"> <div class="card" style="width: 18rem;"> <img v-bind:src="book.Item.largeImageUrl" alt=""> <p>{{ book.Item.title }}</p> <button class="btn btn-primary" v-on:click="addBook(book)">登録</button> </div> </div> </template> <script> data() { return { // 略 books: [], } }, methods: { // 略 async addBook(book) { const url = '/api/books'; const Book = { title: book.Item.title, author: book.Item.author, publisherName: book.Item.publisherName, isbn: book.Item.isbn, itemCaption: book.Item.itemCaption, gunre: 'Java', // 仮でJavaを入れています largeImageUrl: book.Item.largeImageUrl, } const res = await axios.post(url, Book); } } </script>