esa.ioに分報っぽく投稿するアプリをReactとFirebaseで作った

こういう風に投稿すると(左)、esa.ioにこういう感じ(右)で投稿される分報風のアプリを自分用に年末年始に作りました。

f:id:syou6162:20210102210650p:plainf:id:syou6162:20210102210641p:plain

作った動機

きっと皆さんそうしているように、私も日々ログを残しながら作業をしている。仕事ではscrapboxを使っているが、プライベートではesa.ioを愛用している。プレビューを見つつmarkdownで書けたり、タグとカテゴリがいい感じに使えたりするところが気に入っている。あと、アイコンがかわいい。

ちゃんと作業をするときにはesa.ioにページを作るが、そうでない雑なものも記録したいときが度々ある。例えばこういうの。

  • 今度コンビニ行ったとき、忘れずにXXXを買う
  • 統計の本を読んでて、急に理解が追い付いてきたので忘れない内にメモしたい
  • Splatoonで負け越しが続いたけど、YYYを変えればよくなるんじゃないか

ジャンルも粒度もバラバラで、esa.ioに専用のページを作るのもちょっと違う。こういう時はSlackの分報チャンネル(times_syou6162みたいなやつ)が便利。粒度も内容も気にせず、とにかくここに書き殴っておけばいいという場所になっている。思考停止でここに書き込めばいいという場所はとにかく重要で、ログを残すためのハードルを下げてくれる。ChangeLogメモとかもそういう側面があると思う。しかし、Slackは検索がイマイチだったり、日付毎にログが見づらいといった問題がある。

分報風に書きつつ、esa.ioにちゃんと日付毎に(日報風の)記録が残れば便利なのに...ということで自分が欲しいWebアプリをReactとFirebaseで年末年始に作った。

esa.ioでその日の日報のページを開いて...という動作が必要なく、Webアプリなので出先でiPhoneから気軽に書くことができて便利。見るときにはhtmlで表示しつつ、コピペしたいときにはmarkdownに切り替えるのもボタン一発、というあたりも便利で気にいってる。

びっくりするようなものは特に使っていないが、使った要素技術をメモがてら残しておく。

使った要素技術

Firebase Authentication

誰でも日報を書けたり読めたりしては困るので、ユーザー認証を行なう。自前でユーザー管理を行なうのはダルいので、今回はFirebase Authenticationを使うことにした。

Googleログインを使い、ホワイトリストにマッチする自分のメールアドレスの場合だけ読み書きできるようにする。といっても、認証部分はかなりお手軽にできてしまった。

Firebase Hosting + React

アプリを設置する場所としては、Firebase Hostingを選んだ。デプロイも簡単だし(後述)、その他のおもてなしもよくできていて体験として結構よかった。

フロントはReactを選んだ。自分としてはVueのほうが慣れているが、せっかくだし違うものも使ってみよう(あと世の中的にはReactのほうが使われている...?)ということで今回はReactを選んだ。

やりたかったことを勘と雰囲気のノーガード戦法で作るのはチュートリアルも読まずにさくっとできたが、その後のほうが時間がかかった。コンポーネントに分けるとかTypeScript化するとか綺麗にするには基本に帰ろう...ということで、結局チュートリアルはざっと眺めて回った。デザインは特にこだわりもないので、Material-UIで済ませた。

Firebase Cloud Functions

Firebase Hostingだけではesa.ioのデータの読み書きができないので、バックエンド的なものを用意する。自分が使うだけだし、一番お手軽なFirebase Cloud Functionsを使う。AWS Lambdaっぽい感じで使える。言語は何でもいいが、Node.jsで書いた。

Firebase Cloud Functionsをそのまま使うと、エンドポイントが外の人に知られると誰でも叩き放題になってしまう。どうにかする。

Firebase Authenticationを使っている場合、リクエスト時にbearer tokenが自動的に付与される。tokenが付与された場合、functions側でcontext.authにemailなどのユーザーの認証情報が付与されるため、利用できるユーザーを制限できる。bearer tokenがそもそも付与されていなかったり、invalidなものだった場合はFirebase Cloud Functions側でリクエストを弾いてくれるので安心。Firebaseで統一されていることのありがたみを感じる。

esa.ioのAPIキーやteam_nameをコードに保存したくないので、どこか安全な場所に保存してから呼び出したいが、この辺もおもてなしがある。AWSのパラメータストアみたいなもの(違うかもしれない...)があって、こんな感じで情報を保存しつつ

firebase functions:config:set esa.team_name="MY_TEAM_NAME"

アプリ側ではこんな感じで読み込むことができる。

const teamName = functions.config().esa.team_name;

Firebase Cloud FunctionsはHostingと比べるとデプロイの時間が少し長い(といっても1分くらい)ため、firebase側に反映してから試すというのはやりにくい。しかし、この辺もよくできていて手元でエミュレートするモードがある。TypeScriptを使っている場合はtsc --watchをしておくことを忘れずに(修正が反映されないので)。

firebase serve --only functions

これを使うと、5000番ポートにfunctionsをエミュレートしたサーバーが立つので、フロント側も切り替えてあげれば手元で動作確認が済んでとてもやりやすかった。

// 元々はこっち
// const getDailyReport = firebase.functions().httpsCallable('dailyReport');
// ローカルで試したいときはこれを使う
const functions = firebase.functions();
functions.useFunctionsEmulator('http://localhost:5000');
const getDailyReport = functions.httpsCallable('dailyReport');

デプロイ自動化

最初は手元からHostingとFunctionsそれぞれにdeployしていたが、人手でやるのは面倒なのでmasterブランチにマージされたら勝手にデプロイされて欲しい。Firebaseはこの辺もよくできていて、CIに必要なtokenを発行してくれるコマンドがある。こいつをgithubのsecretsに登録して、github actionsに書いてやればデプロイも自動化できる。30分くらいで自動化できて、最高だった。

所感

Firebaseは便利便利と聞いていたもののどう便利か分かっていなかったが、実際使ってみるとなるほど便利というのがようやく実感できた。別の趣味サイトで似たようなことをするときに、cognito + amplify + apigateway + lambdaを使ったけど、えらく難しくて週末でがっとやる感じではあまりなかった。今回は「あの苦労は何だったんだ...」というくらい簡単にできた。

今回は裏側がesa.ioだけど、S3でもGoogle Spreadsheetでも同じようなことはできる。似たようなものを作るときは今回のを真似しつつ、必要なときは今後はバシバシ使っていこうと思った。

2020年はあまりこういう瞬発力系のものを作っていなかったので、2021年はもっと色々作っていきたいですね。