【個人開発】Next.js + Honoで暗号しか投稿できない匿名掲示板を作りました
暗号掲示板というサービスをNext.js + Honoで作ったので紹介します。
同じ技術構成でサービスを開発する人の参考になれば幸いです。
暗号掲示板とは
暗号しか投稿できない匿名掲示板です。
アカウント登録は必要なく、誰でも簡単に暗号化を体験できます。
この暗号掲示板はオリジナルの暗号を投稿するのではなく、RSA暗号によって暗号化された暗号文のみが投稿できるようになっています。
利用には公開鍵暗号についての知識が必要ですが、簡単な解説はサイト内に用意しているので安心です。
詳細はこのページに書いてあるので、ぜひ見てみてください。
また、ソースコードはGitHubで公開しています。
要件定義
暗号が投稿できる掲示板を作ろう!と思いついたので、さっそく要件定義に取り掛かります。
といってしっかりやるわけではなく、暗号掲示板がどんなものなのか自分の中でイメージを固める感じです。
すでにNext.jsプロジェクトを作成していたので[1]、要件はREADME.md
に書くことにしました。
ここに暗号掲示板の概要と、具体的にどんな機能が必要かを書いていきます。
コンセプト
開発したいシステムが何を追い求めるのかを明確にすることで、そのシステムの価値がわかります。
また、どんな機能が必要かやどんなデザインにするかが見えやすくなることもあります。
そのため、コンセプトは基本的にどんな場合でも決めておくべきだと思っています。
暗号掲示板は「暗号ってロマンあるよね」という素人による考えがベースになっています。
そのため利便性は捨て、かっこよさを優先する方針で開発します。
サブテーマ
ついでにデザインコンセプトとしてハッキングを採用しようと思います。
これは「暗号とハッキングはだいたい同ジャンル」[2]という素人の考えによるものです。
ロマンがあるという意味で、イメージはハッキング映画が近いと思います。
そのため実際のハッキングがどんなものなのかによらず、ハッキングといえば感あるあの黄緑色をメインカラーにします。背景はもちろん黒です。
必要な機能
コンセプトが決まったのでシステムの中核となる機能の詳細を決めます。
今回は時間の都合で中核以外の機能は実装しないので、これがすべての機能になります。
- 平文を暗号化して投稿する
- 投稿一覧を表示する
- 暗号文である投稿を復号し、平文として表示するボタンを置く
- あらかじめ生成しておいた鍵(デフォルト鍵)を表示する
投稿
投稿は以下の要素を持っています。
- 本文となる暗号文
- 投稿日時
- 暗号化に使用された鍵のハッシュ値
ここでのハッシュ値は、公開鍵や秘密鍵から計算できる一意の値です。
公開鍵と秘密鍵のハッシュ値を同じにすることで、ハッシュ値を鍵ペアの識別に使うことができます。
署名によるユーザーの識別
当初は署名を利用した公開鍵認証でユーザーの識別ができるようにしようと思っていました。
ですが、時間と体力が足りず断念しました。
開発は計画的に行いましょう。
最終的に出来上がったREADME.md
がこちらです。
(一部は開発中や開発終了後に書いています)
技術選定
今回Next.jsを使った開発は初めてなので、必要以上に複雑にしたら簡単に詰みます。
そのため、できるだけシンプルにすることを心がけました。
また、予算は0円であるのも重要です。
共通
言語
フロントエンド・バックエンドともに、コードはTypeScriptで書かれています。
これはデプロイ先の都合上、バックエンドもNext.js上で動かすためです。(後述)
私がちゃんと書ける言語がTypeScript(とJavaScript)のみなのもあります。
エディタ
使い慣れたVSCodeを採用しています。
VSCodeはTypeScriptを書くときに優秀で、特別な拡張機能なしでもコードハイライトや補完ができます。
また、後述するDockerの管理が、Docker
という拡張機能によって楽になるのも理由の一つです。
今回は個人開発なので、VSCode以外のエディタでの開発は考えないことにします。
暗号化
こちらを採用しています。
- 暗号方式: RSA
- 暗号化: Web Crypto API
暗号方式にRSAを採用した理由は、最初に思いついたから以外にありません。
しかし実際に実装してみたところ、RSAだと平文の長さに限界があったので、共通鍵暗号(AESなど)にすればよかったと後悔しています。
暗号化にWeb Crypto APIを採用しているのは、使ったことがあるから以外にありません。
ですがブラウザでもNode.jsやBunでも動くため、結果的に開発工数の削減に繋がりました。
本番環境
暗号掲示板は、フロントエンド+バックエンドの両方ともVercelでデプロイしています。
同じプラットフォームを使う理由はこちらです。
- 複雑さが増すと今後管理できなくなる
- 無料で半永久的に使えるプラットフォームは多くない
- 別のプラットフォームにすると学習コストがつらい
学習コストがつらいことの詳細
VercelはNext.jsと相性がいいと聞いたので、Next.jsの採用を決めたときからVercelでデプロイしようと思っていました。
しかしVercelを使うのは初めてな上、別でデータベースについても考えなければいけません。
そのため、バックエンドもVercelが使えるなら使いたかったのです。
Vercelを使うのは初めてでしたが、結果的に採用してよかった点はいくつかあります。
- 難しい設定をしなくても、GitHubのPRをマージするだけでデプロイが始まる
- 本番環境とは別にプレビュー環境がある
- 有名なのとドキュメントがしっかりしているので、情報が多い
データベース
暗号掲示板では、投稿された暗号文をどこかに保存しておく必要があります。
色々考えた結果、使ったことのあるPosgreSQLを採用することにしました。
PostgreSQLのデータベースとして、Neonを採用しています。
Neonは無料枠があるデータベースで、VercelやORMのPrismaと連携できるのが特徴です。
他にもSupabaseが候補にあったのですが、コールドスタートがきつそうだったので断念しました。
フロントエンド
前述した通り[1:1]、フレームワークはNext.jsを採用しています。
今回の開発はNext.jsがメインと言っても過言ではありません。
CSS
Tailwind CSS v3とTailwind Variantsを採用しています。
Tailwind CSSの採用理由はこんな感じです。よく見る理由だと思います。
- クラスベースがコンポーネント指向と合っている
- Next.jsのテンプレートに組み込まれているので、導入が不要
- v3: 開発開始が2024/12だったので、現行のv4は出ていなかった
しかし、Tailwind CSS単体で使おうとすると、どうしてもスタイルの優先度が辛いです。
今回はある程度手抜きする方針のため、className
をprops
で手渡しすることが頻繁に発生します。
当初はTailwind CSSのみで実装していたのですが、className
で渡されたスタイルが優先度によって反映されない事故が発生しました。
それを解決するため、あわせてTailwind Variantsを採用しました。
その他
今回の暗号掲示板には以下の処理がありますが、ライブラリなしで実装します。
理由は学習コストを抑えるためです。すでにNext.jsの学習コストが重いので、これ以上は避けます。
- 投稿時刻の表示: 標準の
Date
でがんばる - フォームのバリデーション: 実装しない(手抜き)
バックエンド
HonoをNext.js上で動かします。
今回はHonoをバックエンドのフレームワークとして使います。
Honoの採用理由は使ったことがあるからでもありますが、前述したデプロイ先の都合が大きいです。
というのも、Hono公式から提供されているアダプターを使うと、HonoアプリをNext.jsのRoute Handlersとして動かせます。
するとNext.jsをデプロイするだけでHonoが動くので、バックエンド用にサーバーを用意する必要がありません。
なお、Next.jsのRoute Handlersをそのまま使う方法は、学習コストの懸念から見送りました。
ですがこちらでも大きな問題はなかったと思います。
その他
- バリデーション: Zod
- ORM: Prisma
どちらも採用理由は使ったことがあるからです。
設計
技術スタックが決まったので、次は設計に入ります。
といってもしっかりやるわけではなく、方針を決めておくべき所のみ考えています。
システム設計
以下の2点を実装前に設計しました。
- APIのURL設計
- データベースのテーブル設計
API設計
API設計では、どのパスやHTTPメソッドがどの操作を担当するかを決めました。
今回はリソースが投稿しかなかった上、投稿の更新や削除(CRUDのUD)は実装しないので、考える量は少なくてすみました。
設計の結果、以下の2つのAPIエンドポイントができました。
-
POST /api/posts
: 投稿を新しく作成する -
GET /api/posts
: 投稿一覧を返す
また、各APIエンドポイントのレスポンスやリクエストボディも設計しました。
しかしいざ実装してみると考慮漏れが見つかり、修正が多方面にわたって面倒だったので、しっかり設計することがいかに大事かがわかりました。
データベース設計
データベース設計では、Mermaidにリレーションが書ける図があったので、これを使って書いてみました。
ですがテーブルが一つしかなかったので、今考えると通常の表で良かった気がします。
設計の結果、投稿を保管するposts
テーブルはこうなりました。(一部編集済)
カラム名 | 型 | 用途 |
---|---|---|
id |
数値 | 主キー |
encrypted_body |
文字列 | 暗号文 |
public_key_digest |
文字列 | 鍵のハッシュ |
created_at |
日付 | 投稿日 |
この設計は途中で破綻せず、最後までマイグレーション無しで切り抜けられました。
もしマイグレーションが発生した場合、やり方を調べるための学習コストがそれなりにあったと思うので、時間を取って設計してよかったです。
これ以外にもディレクトリ構成を考えたりはしていましたが、設計書がないので省きます。
また、Next.jsのコンポーネント設計はありません。
デザイン
暗号掲示板はある程度ちゃんとしたデザインにしたいと思ったので、実装前にFigmaを使ってデザインを考えました。
また、同時にどんな画面やモーダルが必要かも考えました。
先ほどコンポーネント設計はないと書きましたが、デザイン時に必要な画面を考えていたおかげでなんとかなったのかもしれません。
開発前にちゃんとデザインを考えるのも、Figmaの使用も初めてでしたが、結果的に満足いくデザインになったのでよかったです。
少なくとも私は実装前にデザインを挟むといい感じになりやすいことがわかりました。
振り返り
その後は紆余曲折ありつつも実装が終わり、無事に公開することができました。
ということで、最後に軽く振り返りをして終わりたいと思います。
完成できた
振り返ったときに一番良かったのは、何と言っても完成まで持っていけたことです。
システムをリリースまで持っていくのはすごく難しいし大変です。
簡単なものでさえ、一つ作るだけでも結構な労力がかかります。(最近はAIとかありますが...)
なので、完成しただけで誇っていいと私は思っています。
しかも今回の暗号掲示板は、メインのNext.jsを使う開発が初めてだったので、その点も大変でした。
それ以外にもデプロイ先のVercelや、データベースのNeonなど、インフラ周りも初見だったので、ちゃんと動く状態でデプロイできて本当によかったです。
スケジュール管理の重要性
...と言ったのですが、実装しきれなかった機能もあります。
これは想定していたより時間と体力が足りなかったからです。
そして、暗号掲示板の開発ではスケジュール管理を全く行っていませんでした。
いつ完成するかの見通しも立てず、割と行き当たりばったりだったと思います。
初めはそれでもなんとかなると考えていましたが、蓋を開けてみれば当初考えていた機能さえ実装しきれませんでした。
もしここで適切なスケジュールを立てていれば、まだなんとかなった可能性は高いです。
今回体感した見積もりを立てないデメリットはこんな感じだと思います。
- いつ完成するかわからないせいで、無駄に気力と体力を削られる
- なんとかなると楽観的に考え、1週間ほどの休憩時間を設け始める
- 体力のペース配分を間違え、↑のような休憩が必要になる
今回の反省を活かして、次は上と同じ失敗を繰り返さないようにしたいです。
最後に
よければ見ていってください。
Discussion