📖

⚙️ Supabase × Render で作る開発支援チャット「DevLinkHub」開発ログ α版までの道

に公開

🧩 本記事は、現在開発中のWebアプリ「DevLinkHub」のα版開発記録です。
実装途中の部分も多いですが、「個人開発をどのように形にしていったか」を記録としてまとめています。

初めに

普段 Google Chat を使う中で「チャンネルを作れない不便さ」を感じたのがきっかけです。
GitHub の Wiki や Issue のように、軽いやり取りと記録を統合できるチャットツールを目指しています。それによりもっと快適な開発ライフが送れることを願っています。

🚀 開発の現状

プロジェクト名: DevLinkHub

今後のアップデート予定

  • ✅ メッセージ保存機能(実装中)
  • 🧩 TODOリスト機能の実装
  • 🪶 軽量メモの実装
  • 🚀 α公開に向けた動作テスト

デプロイサービスについて

使用したデプロイサービスはRender.comです

https://render.com

項目 内容
対応言語 HTML & CSS & JavaScript, Node.js, Python, Ruby, Go, Rust, Elixir, Docker, PostgreSQL
無料枠の制限 15分間アクセスがないと自動的にスリープ状態になる
月750時間まで稼働可能
PostgreSQLは1GB
スペック CPU:0.1
RAM:512MB
ストレージ:不明
通信速度 ping:10ms
ダウンロード速度:219Mbps
アップロード速度:109Mbps

Render.comを選んだ理由

  • 無料
  • クレジットカード登録不要
  • 商用利用可能
  • 24時間365日動かすことが可能
  • 多くの言語に対応しているため
    あまり多くのデプロイサービスを試していないためわかりませんがpythonが使用可能なデプロイサービスはGoogle cloudぐらいしか知らないためこれにしました。

メリット

  • 簡単にデプロイできる
  • たくさんの言語に対応しているため個人開発などに向いている
  • ファイルサイズに特に制限がないためファイルサイズを気にしなくていい
  • 外部ライブラリーを気にしなくていい(デプロイ時に入れてくれる)

デメリット

プログラムが生成したファイルはスリープに入ると削除される
1つ以上のprojectを動かすと24時間365日稼働ができなくなる

使ってみた感想

まだサービスを運用していないため詳しいことは分かりませんがここまで無料枠で提供してくれるんなんてとても太っ腹だと思います

  • デプロイがgithubとの連携だけで行えるためとても楽
  • 多少起動に時間がかかるか許容範囲内

databaseについて

使用したdatabaseはsupabaseです。supabaseとはオープンソースのFirebase代替として設計されたバックエンド即サービス(BaaS)だそうですがそんなこと言われても自分にはわかりません。簡単に言えばFirebaseというgoogleが運営しているdatabaseの代替として作られたものということです。

https://supabase.com

supabaseの無料枠の内容

項目 内容
月間アクティブユーザー(MAU) 50,000人まで
データベース容量 500MBまで
ストレージ容量 1GBまで
APIリクエスト数 無制限
プロジェクト数 同時に2つまで
休止条件 週間アクセスがないと自動休止(再起動可能)
超過時の挙動 読み取り専用モード
クレジットカード 不要

firebaseの無料枠の内容

項目 内容
データベースの容量 1 GiB
ドキュメントの読み取り 50,000/日
ドキュメントの書き込み 20,000/日
ドキュメントの削除 20,000/日

supabaseを選んだ理由

  • 無料
  • クレジットカード登録不要
  • 商用利用可能
  • 24時間365日動かすことが可能
  • APIリクエスト数に制限がないこと

開発していて学んだことなど

🧠 学習編(HTML・CSS・JavaScript)
🧩 技術編(Supabase・Python)
⚙️ 環境編(Render・デプロイ・キャッシュ対策)
📜 手続き編(電気通信事業届出書)

HTML,CSS入門してやったこと

ステップ1:初めてのサイト作り

まずは以下のサイトで HTML と CSS の基礎を学びました。

https://web-design-textbook.com/makepage/basic/

HTMLタグの基本的な書き方や、CSSでレイアウトを整える方法を理解できたところで、
自分なりに以下のようなWebサイトを作成しました。

自作サイト(homepage-cb3.pages.dev)

ステップ2:調べるときに使ったサイト

HTMLやCSSの文法を調べるとき、最初のうちは「どう検索すればいいのか」が分からず、
ChatGPTに頼ることが多かったです。

そんな中で出会ったのが、
「本格的に学びたい方向けWeb制作の学習サポート」でした。

https://web-design-textbook.com/supports/


個人的な評価

参考画像

項目 評価
調べやすさ
見やすさ

なぜこのような評価なのか

例えば、
「メニューボタンの開閉時に「=」から「×」へアニメーションしながら変化させる方法」
といった記事のように、タグ名や専門用語が分からなくても検索しやすい構成になっています。

また、イラスト付きで視覚的に理解できるため、
特にグリッドレイアウトを作成するときなどにとても助かりました。

該当ページはこちら:
グリットレイアウトの解説ページ


メリットとデメリットまとめ

メリット

  • 図解と実例が多く、初心者でも理解しやすい
  • 難しい用語を知らなくても目的の情報にたどり着ける
  • 各章が体系的に整理されていて、学びやすい構成

デメリット

  • コンテンツ数がまだ少なく、すべての疑問を網羅しているわけではない
  • 特定のテーマに特化しているため、幅広いトピックを学ぶには他のサイトも併用が必要

javascriptでよく使ったコード

ページの読み込み後に JavaScript ファイルを動的に追加する

このコードは、ページの読み込み後に JavaScript ファイルを動的に追加する方法です。
後から要素を追加したり、必要なときだけスクリプトを読み込みたい場合にとても便利です。
ボタンを押して読み込む方法もありますが、この方法なら自動で読み込まれるので手間が省けます。

<script>
    const uuid = self.crypto.randomUUID();
    script.src = "{{ url_for('static', path='js/test.js')}}?v="+uuid;
    script.type = "text/javascript";
    target.insertAdjacentElement("afterend")
</script>

コードの解説

UUIDを生成する

const uuid = self.crypto.randomUUID();

jsファイルの場所の指定とエクリ文字列の指定
'static':HTML,CSS,jsのファイルが入っているファイル
v=:エクリ文字列の指定することによってキャッシュを避けることができる

script.src = "{{ url_for('static', path='js/test.js')}}?v="+uuid;

ファイル形式の指定今回はjsのファイルなため"text/javascript"と指定します。

script.type = "text/javascript";

jsファイルを読み込みます今回は"afterend"なのでHTMLが読み込まれた後に実行されます。

target.insertAdjacentElement("afterend")

要素追加

後から要素を付け加える時に使うコードです。
以下のコードはjQueryを使ってbodyにHTMLcodeの要素を追加するコードです。もちろんbody以外にも好きなところに追加することができます。

$("#channel").append(`HTMLのコード`);

websocket

https://zenn.dev/peter_norio/articles/fb64b748d5742e

websocketの仕組みが絵でとてもわかりやすくなっておりしかもよく使う接続方法とmessageを受信した時の書き方などをよく忘れてしまうのでこのサイトはとても重宝しています。(大敗の場合node.jsの記事しか出てこない)

const webSocket = new WebSocket('ws://localhost:3000');

webSocket.addEventListener('open', () => {
    console.log('接続成功');
    webSocket.send('{"message":"send data"}');
});

webSocket.addEventListener('message', (event) => {
    const data = JSON.parse(event.data);
    alert(data.message);
});

webSocket.addEventListener('error', (event) => {
    console.log('error');
});

webSocket.addEventListener('close', (event) => {
    console.log('close');
});

pythonでsupabase使ってみた

初期設定

ライブラリーのインストール

pip install supabase
pip install load_dotenv

supabaseのセットアップコード

from supabase import Client, create_client#supabaseを利用するために必要なライブラリー
import os#.envからdataを取り出すために使う
from dotenv import load_dotenv##envファイルのloadのために使う

load_dotenv()#envファイルのload
os.getenv("supabaseurl")#supabaseurlに入ってるdataの取得
key=os.getenv("supabasekey")#supabasekeyに入ってるdataの取得
Supabase: Client = create_client(url,key)

supabaseの登録

サービスの作成

  • Name:サービス名の入力
  • Type of Organization:組織のタイプを選択

projectの作成

  • Organization:組織の選択
  • Name:projectnameの入力
  • Database Password:databaseのパスワードを設定(一度も使ったことはない)
  • Region:地域を選択(Tokyoがある)
    画像.png

テーブルの作成

  • name:テーブル名の設定
  • Description:説明を入力
  • Enable Row Level Security(RLS):一般のkeyではデータ追加等をできなくする
  • Enable Realtime:リアルタイムで更新してくれるようになる
  • Columns:列の設定
    画像.png

URLの取得方法

project設定の中のData APIにあります
画像.png

keyの取得方法

project設定の中のAPI Keysにあります
anon pubricと書いてある方はRLSは有効な場合書き込み等ができません。しかしservice_rolesecretではできますなので使い方には注意してください

画像.png

データの挿入

response = (
    Client.table(table)#保存するtable名
    .insert({column:data})#保存するdataと保存するcolumn(場所)
    .execute()
)

データを読み取る

response = (
    Client.table(table)#保存するtable名
    .select("*")#テーブルの全てdataを選択
    .execute()
)
jsondata = json.dumps(response.data,ensure_ascii=False,indent=2)#ensure_ascii=Falseすることにより日本語記号などもそのまま描画する,indent=2人間に読みやすいようにする
response = json.loads(jsondata)#strからjsonに変換

データの削除

response = (
    Client.table(table)#保存するtable名
    .delete()
    .eq(table, value)#削除するものの指定
    .execute()
)

Jsonバージョン

response = (
    Client.table(table)#保存するtable名
    .update({overwrite:data})
    .eq(f"{table}->>{table}",former)#削除するものの指定
    .execute()
)

supabaseでよく使うif文

Supabaseでは、データの保存や取得時に「対象が見つからなかった場合」に response.data に空のリスト [] が入ることがあります。この性質を利用して、データが存在しない場合に独自のエラー処理や通知を行うことができます。

Supabaseは基本的に構文エラー以外のエラーを返さないため、このようなチェックを入れておくと便利です。

if response.data == []:
    # データが存在しない場合の処理
    raise ValueError("対象のデータが見つかりませんでした。")

pythonでエラーハントリングしてみた

try,except

一般的な方法でとても便利な方法です。
内容としては、エラーが発生してもコードを終了させずに別の処理を実行できます。
try,except使い方

try:
    print(Hello)
except Exception as e:#大抵のエラーを全て認識してくれる
    #エラー発生時の処理
    print("エラーが発生しました",e)#エラーを表示させる

Hello は未定義なので NameError が発生し、
except Exception as e の後のコードが実行されます。
as e によって、発生したエラー内容を取得できます。

raise

こちらも一般的な方法でとても便利な方法です。
raise はエラーを意図的に発生させるための構文です。
例えば、想定外の値を検知した時に、自分で定義した例外を発生させることができます。

class errorname(Exception):
    pass
raise errorname("意図的にエラーを発生させました。")

クラスを定義することで、エラー名を自由に設定できるため、
エラーハンドリングをより柔軟に行うことができます。

errorname: 意図的にエラーを発生させました。

inspect

inspectは実行元の関数を取得したり動的関数を取得したりなどかなり便利な標準ライブラリーです。

inspect は関数・クラス・モジュールなどの「内部情報」を取得できる標準ライブラリです。
実行元の関数を調べたり、ソースコードを取得したりできます。

引用

実行元の関数取得

この機能を使うことによってどこの関数で実行されたかがわかる様になる。
mainのコードから実行してた場合<module>,関数の中で実行していた場合関数名と表示される

import inspect
def main():
    caller = inspect.stack()[1]  
    print(caller.function)
main()

おまけ

inspect.getsource()は動的関数を取得するために使われていて用途はプラグインなどの外部プログラムを動かす時に使われる

# 関数のソースコードを取得
source_code = inspect.getsource(my_function)
print(source_code)

# そのソースコードを動的に実行
exec(source_code)

個人開発で実際に使ったセット

try,except X raise

try,exceptとraiseを組み合わせるとエラーをとても見やすくできます。
そしてtracebackを使用することによって一番重要な詳しいエラー内容を表示してくれる様になります。

import traceback

class errorname(Exception):
    pass

try:
    print(Hello)
except Exception:#大抵のエラーを全て認識してくれる
    #エラー発生時の処理
    raise errorname(traceback.format_exc())
errorname: Traceback (most recent call last):
  File "ファイルパス", line 7, in <module>
    print(Hello)
          ^^^^^
NameError: name 'Hello' is not defined

まとめ

  • try, except:基本のエラー処理構文
  • raise:意図的なエラー発生やカスタム例外の作成
  • inspect:関数やクラスの情報を動的に取得
  • traceback:詳細なエラー内容を整形して表示

これらを組み合わせることで、開発中のバグ特定が格段に楽になります。

キャッシュ対策

キャッシュを無効化する方法

開発中にコードを更新してもブラウザのキャッシュが原因で反映されないことがあります。
その対策として、拡張機能 ModHeader を使い、localhost のキャッシュを無効化しました。

ずっとコードを変えたはずなのに反映されない場合、キャッシュを削除したら直ったので、今後同じ問題が起きないようにこの設定を入れています。

電気通信事業届出書について

クローズドチャットを実装するには電気通信事業を申請しないといけないことを知ったので申請してみました

電気通信事業届出書(様式第8)について

https://www.soumu.go.jp/main_content/000792999.pdf

電気通信事業届出書(様式第8)に書くことは以下のとおりです。

  • 郵便番号
  • 住所
  • 指名
  • メールアドレスもしくは電話番号
  • 提供区域
  • 事業開始予定年月日(提出日よりも後)
    意外にも、必要な情報はこれだけです。

ネットワーク構成図(様式第3)について

https://www.soumu.go.jp/main_content/000792386.pdf

名前の通りネットワーク構成図を書く場所です

提供する電気通信役務に関する書類(様式第4)について

提供するサービスがあっているものに丸をつけていきます。
chatツールの場合

  • 31 インターネット関連サービスのみ
    に⚪︎クローズドチャットと書きます

</details>

<details open><summary>その他</summary>

住民票の写しの発行に必要なもの

  • マイナンバーカード
  • 200円
    やり方は下のデジタル庁ニュースのやり方動画をご覧になってください

https://www.youtube.com/watch?v=RKA2jaKSebg

切手について

重さ 値段
50g以内 140円
100g以内 180円
150g以内 270円
250g以内 320円
500g以内 510円
1kg以内 750円

自分の場合50g以内だったので送り用の切手1枚と返用の切手1枚買いました

🧭 まとめ

ここまでの開発を通して感じたのは、
単に「便利なツールを使う」ことよりも、
自分の手で “理想の開発環境” を作る面白さでした。

DevLinkHub の開発を進める中で、
以下のような実践的な学びを得ました。

  • Supabase を使った軽量なデータベース構築
  • Render.com を活用した低コストなデプロイ
  • Python × JavaScript による柔軟なバックエンド設計

これらを通じて、
「ツールに合わせて開発する」から
「開発に合わせたツールを作る」 という考え方へと変わっていきました。

まだ α 版の段階ですが、
「開発をもっと軽く、もっと自由に」 をテーマに改良を続けていきます。
最終的には、GitHub や既存サービスに縛られず、
開発者が自分のペースで成長できる環境を目指しています。

この記事が、同じように個人開発を進めている方にとって
小さなヒントや励みになれば嬉しいです。


💡 DevLinkHub α版 はまだ試行錯誤の途中ですが、
「開発の自由を取り戻す」ための第一歩として、
これからも開発の記録を残していきます。

Discussion