はじめに
TypeScriptの学習が一区切りし、Supabaseを使ったDB連携やユーザー認証機能についても一通り学習できたため、これまでに作成したアプリにそれらを導入してみようと思い、本アプリを作成しました。
今回は、アプリを Vercelでデプロイするところまで 実装でき、個人的にも大きな達成感を得られました。一方で、実装の途中では多くの壁にぶつかり、想像以上に時間を使った部分もあります。
本記事では、特に詰まったポイントや学びが大きかった部分を中心にまとめていきます。
なお、見出しの横に ⭐️ を付けている箇所は、教材にはなかった 自分なりにアレンジした実装 です。

参考教材
くるしば様
いつも本当にありがとうございます。正直、講座の内容はかなり難しかったですが、その分学びが非常に多く、確実に力がついていると感じています。
TypeScript化によるファイル設定の見直し⭐️
React + JavaScriptで作成していたファイルのため、まずはTypeScriptに対応させることが大変でした。
作成する手順としては、
・tsconfig.jsonを生成
→ 型の情報などを記述する。後に、tsconfig.app.jsonとtsconfig.node.jsonとさらにファイルを増やし、役割ごとにしました。
・jsやjsx拡張子を書き換えていく
→単純に書き換えるだけやん!と思っていましたが、書き換えるたびに、型が入力されていない赤波線やそもそもコードの書き方が違うというエラーが頻発しました。今となれば分かりやすく指摘してもらえてありがたい。笑
・useAtom()の初期値の設定
→学習している内容はリスト化して配列にしたかったのですが、初期値で空の配列を設定しておらず、型の設定のみしており、沼りました。
型を設定していれば、いけるだろうという安易な考えがよくなかったです。
import { atom, useAtom } from 'jotai';
import { Study } from './study.entity';
// 配列であるため初期値は絶対必要
const studyAtom = atom<Study[]>([]);
export const useStudyStore = () => {
const [studys, setStudys] = useAtom(studyAtom);
// 入力したリストを空っぽにする
const clear = () => setStudys([]);
return { studys, setStudys, clear };
};
認証機能のまとめ方
認証関連の処理を別ファイルにまとめることで、コード全体がかなり整理され、管理もしやすくなりました。
教材のコードをベースにしつつ、Supabase公式ドキュメントも確認しながら実装を進めています。
ロード画面から進まない?!⭐️
ログイン状態を保持するため、毎回ログイン情報を取得する処理を実装しました。
教材では getSession() が使われていましたが、公式ドキュメントを確認すると
より高速かつ安全に取得できる とされていたため、getUser() を採用しました。
ただし、ここで問題が発生します。
当初は、error が発生した場合に throw する処理を書いていましたが、
未ログイン状態でもエラーを投げてしまい、ロード画面から先に進まなくなる という不具合が発生しました。
原因はとてもシンプルで、
「ログインしていない状態では、ユーザー情報が取得できないのは当然」という点を考慮できていなかったことです。
最終的には、未ログイン時はエラーを投げずに return する ことで解決しました。
// 画面を更新してもログイン状態を維持する機能
async getCurrentUser() {
const { data, error } = await supabase.auth.getUser();
// if (error != null) throw new Error(error.message);
if (error || data.user == null) return ;
return { ...data.user, userName: data.user.user_metadata.name };
},
try/catch文の使用⭐️
入力エラーが発生した際、alert() でエラーメッセージを表示したいと考えました。
最初は Supabase 側の処理で alert() を書いていましたが、
UI とロジックの責務は分けるべき という考えから、以下の構成に変更しました。
バックエンド(Repository)
→ エラーを throw
UI(React)
→ try / catch で受け取り表示
Repository 側
// 学習内容を登録する処理
async create(userId: string, params: { content: string; time: number }) {
//先にエラーを書いておく
if (params.content == '') {
throw new Error('学習内容を書いて下さい');
}
if (params.time < 1) throw new Error('無効な時間です');
const { data, error } = await supabase
.from('studys')
.insert({
user_id: userId,
content: params.content,
time: params.time,
})
.select()
.single();
if (error != null) throw new Error(error.message);
return data;
},
UI側
const addList = async () => {
// UIはReact側で制御するのでtry,catch文で
try {
const newstudy = await studyRepository.create(currentUser!.id, {
content: inputVal,
time: inputTime,
});
setInputVal('');
setInputTime(0);
// 配列の更新は破壊しないように!エラーが出ます
studyStore.setStudys((prevStudy) => [...prevStudy, newstudy]);
setUpdateAt(Date.now()); // データ更新を知らせるために更新した
} catch (e: any) {
alert(e.message);
}
};
責務を分けることで、コードの見通しが一気に良くなりました
合計時間を自動で計算する処理⭐️
この機能は本アプリ独自の実装だったため、最初は考え方が全く分かりませんでした。
まず「DBから必要なデータを取得する」という点に立ち返り、
.eq() メソッドを使って ログインユーザーのデータだけを取得 する形にしています
// 合計時間を取得する処理
async totalTime(user_id: string) {
const { data, error } = await supabase.from('studys').select('time').eq('user_id', user_id);
if (error != null) throw new Error(error.message);
if (data == null) return 0;
const totalTime = data.reduce((sum, record) => {
return sum + record.time;
}, 0);
return totalTime;
},
};
ログイン機能があるからこそ、
「ユーザーごとのデータ管理ができる」という当たり前だけど重要な点に改めて気づけました。
終わりに
TypeScriptを 最初から使って開発する経験 はありましたが、
既存のJavaScriptコードをTypeScriptに書き換える のは今回が初めてでした。
想像以上に戸惑う場面も多かったですが、
実務では既存コードの修正や改善が当たり前に行われると聞き、非常に良い経験になったと感じています。
次は転職に向けて、ポートフォリオとして使えるアプリ を本格的に作成していく予定です。
楽しみ!!!