React+Supabase で簡単なブログサイトっぽいもの作ってみた。|Note-of-Beginning
Note-of-Beginning:Supabase + Reactで簡単なブログサイトっぽいもの作ってみた
🧑💻 本記事で紹介している内容は、React および Supabase 初心者の私が個人で学びながら作成したものです。
表記の誤りや実装上の不備が含まれている可能性がありますが、あたたかい目で見ていただけると幸いです。
Note-of-Beginning は、私が初めて企画から設計・開発までを手がけた、ブログ兼メモアプリです。
📖 名前の由来
Note-of-Beginning という名前には、それぞれ以下の意味が込められています。
- Note → 「メモ」や「記録」
- of → 「〜の」
- Beginning → 「始まり」
つまり、「始まりの記録」や「最初の一歩を刻むノート」という意味を持ち、私自身のエンジニア(プログラマー)としての第一歩を象徴するプロジェクトです。
🛠 プロジェクトの背景
もともとは、HTML / CSS / JavaScript + LocalStorage で、簡単なメモアプリを作ったのが始まりでした。
そのアイデアを発展させて、今回は React と Supabase を使って、ログイン機能・画像アップロード・カテゴリ管理まで備えた、より実用的なブログ管理アプリとして形にしてみました。
🛠 技術スタック
- フロントエンド: React (TypeScript)
- バックエンド: Supabase
- エディタ: Quill
https://quilljs.com/
🎯 このプロジェクトでできること
- 記事の作成 / 編集 / 削除
- 公開・非公開の切り替え
- サムネイル画像のアップロード(Supabase Storage)
- カテゴリの追加・選択(複数可)
- アカウント一覧と削除機能(簡易的)
🧩 データベース構成
このプロジェクトでは、Supabase 上に以下の 3 つのテーブルを使用しています。
🗃 テーブル一覧
テーブル名 | 用途 |
---|---|
accounts |
ユーザーアカウントの管理 |
blogs |
ブログ記事の本体 |
categories |
カテゴリ一覧 |
💾 各テーブルの構造
accounts
カラム | 型 | 説明 |
---|---|---|
id |
uuid |
主キー、自動生成 |
username |
varchar |
ユーザー名 |
password |
text |
パスワード(※暗号化前提) |
created_at |
timestamptz |
登録日時 |
blogs
カラム | 型 | 説明 |
---|---|---|
id |
uuid |
主キー |
title |
varchar |
記事タイトル |
thumbnail |
text |
サムネイル画像のURL(Storage連携) |
content |
text |
本文(HTML形式) |
published |
boolean |
公開状態フラグ |
created_at |
timestamptz |
作成日時 |
category_ids |
int[] |
複数カテゴリのIDを配列で保持 |
categories
カラム | 型 | 説明 |
---|---|---|
id |
int4 |
主キー |
name |
text |
カテゴリ名 |
🧪 SQL
-- ユーザーアカウント
create table accounts (
id uuid primary key default gen_random_uuid(),
username varchar not null,
password text not null,
created_at timestamptz default now()
);
-- ブログ記事
create table blogs (
id uuid primary key default gen_random_uuid(),
title varchar not null,
thumbnail text,
content text not null,
published boolean default false,
created_at timestamptz default now(),
category_ids int[]
);
-- カテゴリ
create table categories (
id serial primary key,
name text not null unique
);
📂 Supabase 側の準備
- Supabase Studio で
thumbnails
バケットを作成 - .env ファイルを作成し、以下のようにプロジェクトの URL と anon キーを設定
REACT_APP_SUPABASE_URL=https://your-project.supabase.co
REACT_APP_SUPABASE_KEY=your-anon-key
🧠 実装のポイントと工夫
✅ カテゴリの複数選択と保存(中間テーブルなし)
const [selectedCategoryIds, setSelectedCategoryIds] = useState<number[]>([]);
await supabase.from("blogs").insert([
{
title,
content: contentHtml,
category_ids: selectedCategoryIds,
},
]);
Supabaseでは int[]
型のカラムが使えるため、中間テーブルを用いずにカテゴリの多対多を実現。
表示時には category_ids.includes(id)
を使ってチェック済み状態にできます。
✍️ Quill でリッチテキスト入力を実現
useEffect(() => {
if (quillRef.current && !quillInstance.current) {
quillInstance.current = new Quill(quillRef.current, {
theme: "snow",
modules: {
toolbar: [
[{ header: [1, 2, false] }],
["bold", "italic", "underline"],
[{ list: "ordered" }, { list: "bullet" }],
["link"],
],
},
});
}
}, []);
エディタは ref でDOMに直接マウントし、投稿時にquillInstance.current.root.innerHTML を保存。 本文はHTMLとしてSupabaseに保存され、編集時は dangerouslySetInnerHTML で表示しています。
🧰 対応している基本機能と特徴
✅ 基本機能
-
見出しの設定
- H1、H2、通常テキストを切り替え
-
文字装飾
- 太字(Bold)
- 斜体(Italic)
- 下線(Underline)
-
リスト
- 番号付きリスト(Ordered list)
- 箇条書きリスト(Bullet list)
-
リンクの挿入
- ハイパーリンクを設定可能
🔧 その他の特徴
-
リアルタイムでHTMLを生成
- 入力された内容は
quill.root.innerHTML
で取得可能 - そのまま Supabase に保存して表示に利用
- 入力された内容は
-
モジュール方式で拡張可能
- ツールバーの構成を自由にカスタマイズ可能
-
テーマ選択が可能
-
snow
(デフォルト)やbubble
など、見た目のテーマ変更に対応
-
※ 現在は「見出し・文字装飾・リスト・リンク」のみ実装済み。
今後、画像挿入やコードブロックなどの機能も追加予定。
🖼 サムネイル画像のアップロード(Supabase Storage)
### 🔧 input
のコード
この関数では、<input type="file">
で選択された画像ファイルを Supabase Storage にアップロードし、その画像の 公開URL を取得して記事のサムネイルに使用しています。
🔁 実装の流れ
-
Date.now()
を使って一意なファイル名を生成 -
.upload()
を使ってthumbnails
バケットに画像をアップロード -
.getPublicUrl()
でアクセス可能な URL を取得 -
setThumbnailUrl()
でステートに反映し、プレビュー表示やDB保存に活用
📝 備考
ファイルサイズ制限や拡張子チェックは行っていないため、
今後のバリデーション強化も検討中です。
🔐 アカウント管理機能(簡易ログイン)
このアプリでは、Supabaseの accounts
テーブル を用いた、デモ用途の簡易ログイン機能を備えています。ログイン機能自体は本格的な認証処理ではありませんが、以下のような最低限のアカウント管理が可能です。
👤 アカウントの作成(ユーザー登録)
新しいアカウントは、以下のようなコードで追加できます:
const handleCreateAccount = async (username: string, password: string) => {
const { data, error } = await supabase.from("accounts").insert([
{
username,
password, // 本番環境ではハッシュ化が必須
},
]);
};
この処理では、accounts
テーブルに新しいレコードを挿入します。
※ セキュリティ上の注意:上記の例ではパスワードが平文で保存されており、実運用には bcrypt 等でのハッシュ化が必要です。
📋 アカウント一覧の取得と表示
登録されたアカウントは、次のように取得して一覧表示できます:
useEffect(() => {
const fetchAccounts = async () => {
const { data, error } = await supabase.from("accounts").select("*");
setAccounts(data || []);
};
fetchAccounts();
}, []);
🗑 アカウントの削除
特定のアカウントを削除したい場合、以下のように実装しています:
const handleDeleteAccount = async (accountId: string) => {
await supabase.from("accounts").delete().eq("id", accountId);
setAccounts((prev) => prev.filter((a) => a.id !== accountId));
};
この関数では:
- Supabase 側で指定された
id
を持つアカウントを削除 - 同時にフロント側の状態を更新して該当アカウントを除外
という2つの処理を行っています。
⚠️ セキュリティ・拡張の余地
現状はシンプルな構成ですが、以下のような拡張を予定しています:
- 🔐 Supabase Auth を用いた 本格的なログイン機能 の実装
- 🔒 パスワードのハッシュ化(bcrypt など)
- 👥 権限管理(Admin / Writer 等)
実際にリリースする場合、これらの機能はほぼ必須といえます。
ただし、この作品は「自分が作ってみたい」と思って始めた学習・実験的なプロジェクトでもあるため、まずは「動くもの」を目指してシンプルな構成からスタートしました。 今後の学びや技術習得に応じて、少しずつ改善・拡張していく予定です。
🖼 画面イメージ(準備中)
今後追加予定です。かなり後になると思います。
📝 まとめ
今回のプロジェクトを通して、Supabase の豊富な機能に驚かされました。
特に、認証・データベース・Storage まで一通り揃っていて無料枠も強力な点が魅力です。
また、TypeScript は最初こそ型の扱いに戸惑いましたが、コンポーネント単位で再利用可能な構造を作れる点が非常に便利で、自分がこれまで触ってきたプログラミング言語にはない良さや面白さを感じました。
Discussion