【個人開発】 技術書籍のレビューや「読みたい」を記録するサービスを作った

## サービス紹介

今年はお正月休みに何かテーマを決めて開発しようと思い、技術書籍のレビューサイトを作りました。

とはいえ実際に開発を始めてみたら大幅にお休みの期間をはみ出してしまい、毎日仕事の後に少しずつ進めていってやっとリリース出来るところまでたどり着いた形です。

[techbooks - 素晴らしい技術に、出会おう。](https://techbooks.jp)

[![techbooks - 素晴らしい技術に、出会おう。](https://techbooks.jp/static/images/og-image.jpg)](https://techbooks.jp)

techbooksといって、人気の技術書籍を集めた一覧からタグで検索したり「読んだ」書籍の感想を書いたり「読みたい」書籍をまとめたりすることが出来ます。

また、タグや書籍は人気順にソートされているので人気のジャンルやその書籍が簡単にわかるようになっています。現在3000冊以上掲載中です。

読んだ技術書の記録を残しておけるようなサービスが欲しいと思っていたので作りました。

ここでは利用している技術スタックや開発中に感じたことなどまとめてみようと思います。

## 開発に要した期間

- サービス構想 3日
- DB設計・データ収集 3日
- インフラ構築 2日
- api作成 5日
- webフロントエンド作成 7日

apiを半分ほど作り終わったあたりでお休みが終了してしまい速度が大幅にダウンしていますが、こんな感じです。だいたい3週間ほどでしょうか。

認証部分にfirebaseを使ったことでかなり楽が出来たと思います。最近のfirebase本当すごいですね。
本当に30分ぐらいでOAuthを使ったログイン機能をサービスに組み込むことが出来ました。

**書籍データの収集**

DB設計・データ収集の部分では最初に書籍のリストを作成するためにQiitaのAPIを利用しています。

Qiitaの記事を20万記事ほど解析しAmazonやオライリージャパンのURLから技術書籍へのリンクと思われるURLを抜き出し、そのURLからASIN(Amazonの商品ID)やISBN(書籍コード)を抽出、AmazonのProduct Advertising APIから書籍画像などのデータを取得しています。

書籍へのリンクがついた各Qiitaの記事からタグも抽出していて検索に利用しています。
例えばこちらの [深層学習 (機械学習プロフェッショナルシリーズ)](https://techbooks.jp/book/深層学習%20(機械学習プロフェッショナルシリーズ)/5c2766c3875aaccac62602d6) という書籍には `#Python` `#機械学習` `#データサイエンス` `#Keras` などのタグがふられています。

書籍リストの作成をどうしようかと考えていた時にこちらの記事 [技術書ランキングサイトをQiita記事の集計から作ったら、約4000冊の技術本がいい感じに並んだ](https://qiita.com/jabba/items/edefda09121877b79760) に出会い、これは素晴らしい方法だと思ってデータ抽出の部分を参考にさせてもらいました。リスペクトです。

## 技術スタック

- React
- Next.js
- Firebase
- MongoDB
- Docker

## サービス構成

### フロントエンド

#### SSR × SPA

React + Next.js でSSRなSPAになっています。
SPAのクライアントサイドルーティング、サクサクとブラウジング出来て好きです。気持ちいいですよね。
dev.toとまでは行きませんがtechbooksもそれなりの速度で画面遷移できるように工夫しています。

SPAのサイトを利用していてよく思うのですが、ブラウザバックした時にスクロールが変になったり画面がフラッシュしたりすることありませんか？

それが地味に嫌だったのでクライアントサイドキャッシュを実装して適宜ブラウザバック時に一瞬で前の画面が再現されるようにしました。

#### フレームワーク

そこまで複雑な状態を扱うアプリケーションではないのでReduxやMobXなどの状態管理用のフレームワークは特に利用せず、認証情報の管理や読んだ・読みたい書籍の登録などAPIコールが発生するアクションの管理にContext APIを使っています。

Reduxを用いずにContext APIだけでアプリを書いたのは始めてだったのですが、小規模なものなら全然戦えますね。ガチガチに状態管理用のコードを書く必要がなくてカジュアルに実装を進められるので気持ちよかったです。

反面、Contextを用いた状態の取り回しは自由度が高すぎて結局オレオレフレームワーク化しやすい印象を受けました。ここらへんうまくやらないとカオスになりそうです。

#### 利用したモジュール

機能も実装もコンパクトにまとめたかったので全体的に出来るだけサードパーティモジュールは利用しない方針にしていて、他には

- [NProgress](https://github.com/rstacruz/nprogress)
    - YouTubeなどにあるような画面遷移の進捗表示プログレスバー
- [rc-slider](https://github.com/schrodinger/rc-slider)
    - スライダーUIコンポーネント
- [react-device-detect](https://github.com/duskload/react-device-detect)
    - モバイルやブラウザなどUAから利用デバイスを判定
- [next-ga](https://github.com/sergiodxa/next-ga)
    - GoogleAnalytics対応

これらのモジュールを利用しているだけです。
CSSフレームワークも特に利用せず素のCSSを普通に書いています。

FlexBoxやCSS Gridなど便利なプロパティがどんどん充実してきてレスポンシブ対応もすごく楽になりましたね。MediaQueryを必要最小限の数カ所に書くだけでモバイル・デスクトップ両対応の画面を作ることが出来ました。

#### OAuth認証

認証部分については自前でOAuthを実装するのが少し手間だったのでそこだけFirebaseを利用しています。

その上で自前のDBでもユーザ管理を行いたかったので認証/ログインの部分だけ完全にフロントエンド側で実装し、ログイン/ログアウトの際にイベント通知をバックエンドに送るようにしました。

ログインイベントが通知されたタイミングでバックエンド側でブラウザにセッションを発行してSSR時にもログイン情報が反映されるようになっています。

クライアントサイドでのログイン処理だけならFirebaseの管理画面でポチポチ設定したあとに生成されたファイルをプロジェクトにインポート、数行のコードを書くだけで実際にOAuthログインする機能を実装することが出来ました。すごい。

ログイン/ログアウト時の処理はこれだけです。

```js
import firebase from 'firebase/app'

firebase.initializeApp(require('../credential/firebase-client.json'))

firebase.auth().onAuthStateChanged(user => {
    if (user) {
        user
            .getIdToken()
            .then(token => {
                // ログイン時の処理
            })
    } else {
        // ログアウト時の処理
    }
})

const handleLogin = () => (
    firebase.auth().signInWithPopup(new firebase.auth.GithubAuthProvider())
)

const handleLogout = () => (
    firebase.auth().signOut()
)
```

今回はGitHubログインのみ実装していますが、メールアドレス/パスワードでサインインする機能も簡単に実装できるようです。

パスワードを忘れた時用の一時URLの発行・メール通知などもデフォルトでついてくる上、SNSログインした際にメールアドレスの重複を許可するかなどの細かい設定やアカウントのリンクなどもできて便利です。

### バックエンド

express + mongooseでMongoDBと通信しjsonを返却するシンプルなapiサーバを立てています。BFF(Backend For Frontend)というやつですね。

今回は出来るだけお正月休み中に実装を進めてしまいたかったので慣れた形のREST apiっぽい感じで実装しました。
あまりRESTfulにはこだわらず、ざっくり/user, /entry, /bookなどリソースごとにパスを切る形でアクセスするようになっています。

次回何かサービスを作る時はGraphQLを使ってみようと思っています。Reactとの相性も良いようですね。

前述のフロントエンドで発行したログイン/ログアウトの通知を受け取る部分の実装は `firebase-admin` というサーバサイド向けのfirebaseモジュールを利用しています。
next.jsのリポジトリにあるこちらのコードがとても参考になりました。

[next.js/examples/with-firebase-authentication/](https://github.com/zeit/next.js/tree/master/examples/with-firebase-authentication)

### データベース

そんなに複雑なデータを扱うわけでもなく、データの整合性が厳密ではなくても成立するサービスなのでMongoDBを採用しました。
3.x系になってから格段に使いやすくなった気がします。

少し前まではちょっと複雑なクエリを書こうとすると使いにくかった印象ですが、最近はaggregationフレームワークがどんどん充実してきてとても良いですね。速いです。

### Dockerコンテナ

フロントエンド・バックエンドは外側にnginxをかませて適宜バーチャルホストの解決やキャッシュの役割を持たせており、裏側では上記3つのサービスが各々独立したDockerコンテナとして起動しています。

今はさくらのVPS上に自前で薄く構築したHerokuライクなシステムの上で動いていますが、コンテナ同士は疎結合なので基本どこへデプロイしてもやっていけるはずです。Docker超便利ですね。

コンテナベースでデプロイが出来るサービスも色々出始めているので次回サービスを作る時はk8sでオーケストレーションする前提でそういったサービスを利用してみたいです。

## まとめ

React + Next.jsでSSR + SPAなサービスを作りましたが、ReactのみでSSRをやろうとした時のしんどい部分をNext.jsがかなり吸収してくれて開発しやすかったです。

今回は認証部分のみFirebaseを使って後は普通にDBサーバ立てる感じで構成しましたが、Firebaseがすごく便利だったので次はFirebase縛りで何か作ってみようと思います。

使ってみたかったけど今回は手を出せなかったGraphQLも使ってみたいですね。あとPWAも対応したい。デプロイ先もなんかモダンなところにバシっとやりたい。Firebase以外のサーバーレス的なサービスも使ってみたい。あと…

…と、個人開発だと締め切りが無いのでついあれもやりたい、これもやりたい、となってしまいがちなので区切りの良いところで次回へのお楽しみに持ち越すというのも大事ですね。

やっぱりサービス開発は楽しいです。画面書くの楽しい。
今回の学びを活かしながらまたテーマを決めて何か作ろうと思います。

もちろん[techbooks](https://techbooks.jp)の開発もこれから続けていきます。良かったら是非使ってみて下さい。

あと最近Twitter始めたのでフォローしてもらえると嬉しいです。

[@YuhsakInoue](https://twitter.com/YuhsakInoue)