翔さんやはっしーとかがSupabaseを利用して何やら面白そうな開発ごとをしています。 今までのウォーターフォール型の開発とは違い、UIから機能要件定義をなんやらかんやら聞こえてきますが、私も負けてはいられない!
ということで、今回は簡単なメモアプリを作成し、SupabaseとPrismaを連携させてデータをデータベースに保存する手順を備忘録としてまとめてみます。Gemini CLIも使っちゃうわよ!
環境:Next.js
SupabaseのAuth、Realtime、RLSは使わず、データベース機能をAPIから利用するため Prisima を採用します。データベースのテーブルを Javascript / Typescriptのオブジェクトのように扱えるのでフロントエンドエンジニアに馴染みのある記述ができるのではないかと。
Prisma
Prisma ORMは、TypeScriptやJavaScriptで利用できるORM (Object-Relational Mapping) ライブラリです。
簡単に言うと、プログラミング言語のオブジェクトで定義したメソッドで、SQLを書かずにデータベースの操作が可能なツールです。
さらにPrismaはDBのスキーマから型を自動生成してくれるので、データベースに存在しないカラムや型違いのデータを操作しようとすると、コンパイルエラーで気づくことができるのです!
Supabase
Supabaseは、オープンソースのFirebase代替として注目を集めているフルスタックバックエンドサービスです。
- PostgreSQLデータベースを基盤とした高い拡張性と信頼性
- リアルタイムデータベース機能でデータ変更の自動通知
- 多様な認証方法(電子メール/パスワード、ソーシャルログイン、電話認証など)をサポート
- ファイルストレージ機能で大容量ファイルを効率的に管理
- RESTful APIとGraphQL APIの自動生成による高速開発
- 開発者向けツールとSDKが豊富に用意され、設定や導入が簡単
Supabaseへの連携手順を重要視したいので、Gemini CLIで見た目の部分を作成してもらいます。これがまた泣かせるほど便利な代物!会社がGoogle Workspaceで良かった!

SupabaseとPrismaでメモ機能を作りたいです。とりあえずpage.tsxを作成してください。


おけ、じゃあ、textareaに入力した内容をSave Memoのボタンを押したらYour Memosにリストで追加するように組んで欲しい

見た目はこんな感じでOKです!入力した内容が下のリストに追加されるようになりました。ただJavaScriptでイベントしているだけなので、リロードするとリストは消えてしまいます。
入力した内容を保持するにはデータベースを使用します!
まずはSupabase側で新規プロジェクト作成をします。
「New Project」のボタンから作成、Database passwordはPrismaでコネクションする際に必要なので控えておいてください。

次にPrismaを連携させます。
npm install prisma --save-dev
npx prisma init
これらのコマンドを入力するとprismaがインストールされ、prismaフォルダが出現。
.envファイルのDATABASE_URLをSupabaseのコネクションURLに書き換えます。 [YOUR-PASSWORD]の部分に先ほどSupabaseで設定したDatabase passwordを当てはめます。
DATABASE_URL="postgresql://postgres.odoaingwnxkhkeujwhtp:[YOUR-PASSWORD]@aws-1-ap-northeast-1.pooler.supabase.com:5432/postgres"
そうしたら/prisma/schema.prisma
にスキーマ(データベースの構造定義)を記述します。
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model Memo {
id Int @id @default(autoincrement())
content String
createdAt DateTime @default(now())
}
model ○○は、任意で命名できます。
スキーマを書いたらマイグレーションをおこないます。
npx prisma migrate dev --name init
migration.sqlファイルが生成され、Supabase側にデータテーブルが作成されます。
Supabaseへ接続するためのAPI作成
入力した値をSupabaseへ保存するために接続用のAPIを作成します。
プロジェクトに Prisma Client をインストール。
npm install @prisma/client
Supabaseへの接続
/libs/prisma.ts
import { PrismaClient } from "@prisma/client";
const Prisma = new PrismaClient(); //インスタンス化
export const main = async () => {
try {
await Prisma.$connect();
} catch (error) {
return Error("DB接続に失敗しました");
}
};
API
/api/memos/route.ts
import { NextResponse } from "next/server";
import { prisma } from '@/lib/prisma';
export const GET = async (req: Request, res: NextResponse) => {
try {
await main();
const memos = await Prisma.memo.findMany();
return NextResponse.json(memos);
} catch (error) {
return NextResponse.json("エラーが発生しました");
} finally {
await Prisma.$disconnect();
}
};
export const POST = async (req: Request, res: NextResponse) => {
const { content } = await req.json();
try {
await main();
const memos = await Prisma.memo.**create**({
data: {
content: content,
},
});
return NextResponse.json(memos);
} catch (error) {
return NextResponse.json("エラーが発生しました");
} finally {
await Prisma.$disconnect();
}
};
GET関数はデータベースを全件取得するAPIで、findMany()メソッドで条件に一致する全てのレコードを取得します。Prisma.memo.findMany()の「memo」はスキーマ名です。
POST関数はデータベースに追加していくAPIで、create()で作成していきます。
他にも削除や更新などのメソッドがあり、SQL文が書けなくてもJavascriptの感覚で操作が可能です。
こちらで一通り準備はできました。
あとはpage.tsxに先ほど書いたAPIを持ってきて組み込めばOKです。
'use client';
import React, { useState, useEffect } from 'react';
interface Memo {
id: number;
content: string;
createdAt: string;
}
export default function MemoApp() {
const [memos, setMemos] = useState<Memo[]>([]);
const [newMemo, setNewMemo] = useState('');
const fetchMemos = async () => {
const response = await fetch('/api/memos');
const data = await response.json();
setMemos(data);
};
useEffect(() => {
fetchMemos();
}, []);
const handleSaveMemo = async (e: React.FormEvent) => {
e.preventDefault();
if (newMemo.trim() !== '') {
await fetch('/api/memos', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ content: newMemo }),
});
setNewMemo('');
fetchMemos();
}
};
return (
<div className="bg-white min-h-screen">
<div className="max-w-2xl mx-auto px-4 py-16">
<header className="text-center mb-12">
<h1 className="text-5xl font-extrabold text-gray-900">Memo</h1>
</header>
<main>
<div className="mb-12">
<form onSubmit={handleSaveMemo}>
<textarea
className="w-full p-4 text-gray-800 bg-gray-100 rounded-lg border-2 border-gray-200 focus:outline-none focus:ring-2 focus:ring-gray-400 focus:border-transparent transition"
placeholder="Create a new memo..."
rows={3}
value={newMemo}
onChange={(e) => setNewMemo(e.target.value)}
></textarea>
<div className="flex justify-end mt-4">
<button
type="submit"
className="px-6 py-2 bg-gray-800 text-white font-semibold rounded-lg hover:bg-gray-900 transition"
>
Save Memo
</button>
</div>
</form>
</div>
<section>
<h2 className="text-3xl font-bold text-gray-800 mb-6">Your Memos</h2>
<div className="space-y-4">
{memos.length > 0 ? (
memos.map((memo) => (
<div key={memo.id} className="bg-gray-50 p-6 rounded-lg shadow-sm">
<p className="text-gray-700">{memo.content}</p>
</div>
))
) : (
<div className="text-center text-gray-500">
<p>No memos yet. Add one above!</p>
</div>
)}
</div>
</section>
</main>
</div>
</div>
);
}


Supabase側に保存されていますね!
今回はGeminiに頼って大まかに作成してしまいましたが、データベースを利用して値を保存できるようにすれば、作成する機能やアプリの幅も広がりますよね!
バックエンドってなんだか難しそう……と先入観がありましたが GUIツールでデータベースを管理できて分かりやすく、データベース連携のハードルが下がりました!
やったー!
マークアップを中心に、JavaScriptやReact、Next.jsを使ってフロントエンドの開発をやっています。自分が関わったサイトが無事に公開されると嬉しかったりします!趣味はギターを弾くこと。コードは書くのも弾くのもどっちも楽しいです!
ひらっち
フロントエンドエンジニア/2022年入社