@御茶ノ水, 東京

Webエンジニアの日記です.

#vim #Go #TypeScript #Nuxt.ts #Haskell

> English

Nuxt + Express で Markdown ベースのブログを開発した

2019/10/13

先月公開したこのブログですが、短時間での開発を目指しつつも、いくつかこだわった点があるので紹介します。

ソースコードはこちらに全て公開しています。

TL;DR

  • Nust + Express 構成にした.
    • Microservices アーキテクチャにおける Backend For Frontoend (BFF) の個人的なサンプルになった
    • サーバー内にリソースを持って Server Side Rendering (SSR) できるので高速化・効率化に繋がった
  • GitHub を最大限活用した Markdown ベースのブログとした.
    • GitHub の Markdown エディタで執筆し、表示もそれに近いものにできた
    • 想像以上に執筆しやすい環境になった
  • いくつか細かい点でこだわった
    • ブラウザ上で、API経由で取得したデータをキャッシュした
    • 多言語対応した
    • インフラに kubernetes, ingress を採用してみた

Nuxt + Express 構成にした

Nuxt + Express 構成とはどういうことか?

Vue をユニバーサルアプリケーションに拡張するためのフレームワークである Nuxt は広く普及していますが、この Nuxt が「Express のミドルウェアとして動く」というのはあまり知られていないのではないでしょうか? 少なくとも自分は割と最近まで知らず、実践TypeScriptを読んで衝撃を覚えた記憶があります。

Nuxt + Express 構成を概念図とすると下のような形になります。 また、 Express は API サーバーとしての機能を持たせることもできます。そのため、ブラウザの上で動いている Vue は、Express が提供する API を叩くことができます。

図1. Nuxt + Express 構成図1. Nuxt + Express 構成


なにが嬉しいのか?

Express を中心に添えることで Nuxt 単体だとできなかったことが可能になります。
(以下の例は、自分が Nuxt 単体で開発・運用した経験から感じた課題とその解決案であり、Nuxt + Express 構成で実際に大規模開発をしたわけではないです)

例1) ビジネスロジックを担当するAPIサーバーが別にあって、そこがマスタデータ(商品カテゴリ一覧とか、地域名一覧とか)を管理している場合

この場合、Nuxt単体だとユーザーからの初回リクエスト時にそのマスタデータを毎回API経由で取得する必要があります。マスタデータが更新されている可能性があるため、毎回通信が発生します。

一方で Express + Nuxt 構成の場合、夜間バッチでマスタを定期更新することもできるし、APIサーバー側からの通知を受けて更新することもできます。またそのマスタデータもメモリに残すもよし、Redisでも良しで自由度が高いです。SSR時はそのマスタデータを直接使用するのでラウンドトリップなしでいけます。

例2) サーバーサイドが Microservices で構成されている場合

バックエンドのサーバーが1つだと、クライアントサイドの Vue から直接そのサーバーを叩けば良いですが、バックエンドが Microservises 構成になっている場合は話が変わります。よく Microservices の文脈では BFF アーキテクチャが紹介されますが、Exprss + Nuxt 構成はこれにぴったりなんじゃないかなと個人的に思っています。

概念的には下の図のようになりますが、Express サーバーがフロントエンド専用の API を用意し、クライアントサイドの Vue は Express の API を叩きます。リクエストされた時は、Express はバックエンドの Microservices と gRPC などで通信しつつ、ビジネスロジックを完了する、という形です。

図2. BFF と Microservices

図2. BFF と Microservices
(出典: TRACK WRESTLING MOBILE APP


個人ブログでのメリット

以上のように、Express + Nuxt 構成にすることで、そこそこ大きい規模のWebアプリケーションにも耐えられ、また BFF としても機能できる、非常に魅力的なアーキテクチャが実現できます。自分のブログは全くそのような必要はないような小さいアプリケーションですが、いつか Microservices で大きいアプリケーションを書くことを夢見てこのようにしてみました。

とはいえ、以下のような点は、個人ブログであってもプラスに働いたと思います。

  • SSR時はサーバー上に保存されたデータを直接読み取るため、API通信が発生しない
  • Vue と (Express の) API で共通の Interface を参照することができる

GitHub を最大限活用した Markdown ベースのブログとした

当初から記事は Markdown で執筆したいと思っていました。 Markdown ファイルから HTML を生成するツールはいくつか存在するようです(cf. Nuxt.js で Markdown ベースのブログを構築する)。しかし、コードスニペットなどまで含めて綺麗に表示をするには CSS の記述を頑張る必要がありました。今回は短期間での開発を目標としていたので、この点についてはバッサリと削減することにしました。代替として GitHub を使用します。

GitHub は優れた Markdown エディタ・ビューワーを備えています。 また、重要なこととして、GitHub の Markdown CSS を MIT ライセンスで使用できるリポジトリが公開されています。GitHub からも公開許可が出ているのでラインセンスの問題は確かに解決されているようです。(GitHubの心意気がかっこいい..)

以上を踏まえ、流れとしては

  1. GitHub 上で Markdown を編集する
  2. Preview で仕上がりを確認する
  3. markdown-css に対応した class が当てられた HTML を取得する

という感じになります。ここで 3番の工程だけ綺麗に解決できず、やむおえず力技ですが dev tool を開いて HTML を取得しています。

ということで、このブログは GitHub を使って執筆しています。使ってみると超快適です。
ブランチを切って commit していけば下書き保存もできます。


図3. GitHub Markdown エディタでの執筆の様子図3. GitHub Markdown エディタでの執筆の様子


いくつか細かい点でこだわった

大した話ではないですが、アプリケーションの一部についてはこだわりを持って開発しました。


ブラウザ上で、API経由で取得したデータをキャッシュした

このブログは Nuxt + Express 構成にしたことで SSR 時は API リクエストを叩いていません。しかし、CSR 時は Express が提供する API を叩いています。
その際に、一度取得したデータを再び取得することがないように、オンメモリでキャッシュする仕組みを備えています。

この Vue プロジェクトでは、API から取得するデータは全て Store に格納するようにしています。
store/post.ts がブログの投稿データを取得・保存する store に当たります。

store/post.ts


export interface IState {
posts: IPostSummary[]
post: Record<number, IPost>
}

export const state = (): IState => ({
posts: [],
post: {}
})

...

export const actions = {
async fetchPost(
this: Vue,
{ state, commit }: any,
id: number
): Promise<void> {
// キャッシュがあれば早期リターン
if (_.keys(state.post).indexOf(String(id)) >= 0) return
...

Store の state.post で TypeScript の組み込み型関数である Record を使用しています。 これによって、 state.post は 「key は記事IDの number 、 value はAPI経由で取得したデータ」 というペアのオブジェクトだと定義づけられます。

( number型 と 記事ID は一対一対応しないので、より突き詰めるならば、記事ID型を宣言して、Record<PostId, IPost> とするべきですが、今回は楽をしています.. )

このように Recod 型の方式で store に保存することで、次に id = N の記事を API 経由で取得する際は、store に id = N の記事が保存されているかどうかを確認して、保存されていない場合だけ API リクエストを送る、ということができます。


多言語対応した

ブログを作るなら多言語対応したいと思っていたので実装しました! 今回はvue-i18nというライブラリを使用することで簡単に多言語対応ができました。kazupon さんが作ってくれた OSS ツールですね、感謝です。

詳しい使い方は調べてもらえばたくさん日本語記事が出てくるので割愛します。


インフラに kubernetes, ingress を採用してみた

これはずっと以前から興味を持って少しずつ勉強していたことなのですが、kubernetes を運用してみたいと思っていたので今回の個人開発で使用してみました。GKE on GCP にデプロイしたのですが、まだまだ kubernetes は初心者なのでほぼデフォルトの設定になっているかと思います。これから時間を見つけてyamlでの設定を勉強したいなあと思っています。


まとめ

以上が自分の作ったブログの紹介になります。 何度かブログを開発してきたのですが、毎回「CMS機能を作り込むのに時間を割けられない」という悩みに直面し、そのため記事が書きづらく、億劫になって投稿しない...という悪循環になっていました。今回はシンプルな実装で保守しやすく、なおかつ GitHub を活用するなど、楽をして品質の良いブログアプリケーションが出来上がったと思っています。

ブログは完成したとしていますが、まだまだやりたいことがあり、コードも出来が悪いなあと思う部分が多いです。この点については、時間をかけて改善していけたらなと思います。

自分はフロントエンドもサーバーサイドも経験豊富という訳ではないので、この記事の内容も間違っている部分があるかもしれません。何かご指摘ありましたら、Twitter か GitHub 経由でコメントいただけると嬉しく思います。