翔先生和Hashi等人正在利用 Supabase 开展一些很有意思的开发项目。与以往的瀑布式开发不同,听说大家是从 UI 开始、一边讨论功能需求一边推进,我也不甘示弱!
因此,本次我们将创建一个简单的备忘录应用,并整理出将 Supabase 与 Prisma 结合、把数据保存到数据库的步骤作为备忘记录。Gemini CLI也一起用上!
环境:Next.js
由于不使用 Supabase 的 Auth、Realtime 和 RLS,而是通过 API 使用数据库功能,因此我们选择 Prisma。它可以将数据库表当作 JavaScript / TypeScript 对象来操作,因此应该可以用前端工程师熟悉的写法来编写代码。
Prisma
Prisma ORM 是一个可用于 TypeScript 和 JavaScript 的 ORM(对象关系映射)库。
简单来说,它是一个工具,可以通过编程语言对象中定义的方法进行数据库操作,无需编写 SQL。
此外,Prisma 会根据数据库的 Schema 自动生成类型,因此在尝试操作数据库中不存在的字段或类型不匹配的数据时,编译错误会帮助你及时发现问题!
Supabase
Supabase 是一款全栈后端服务,作为开源的 Firebase 替代方案备受关注。
- 基于 PostgreSQL 数据库构建,具备出色的扩展性和可靠性
- 通过实时数据库功能,数据变更可自动通知
- 支持多种身份验证方式(电子邮件/密码、社交登录、手机验证等)
- 借助文件存储功能,可高效管理大容量文件
- 通过自动生成 RESTful API 和 GraphQL API,实现快速开发
- 提供丰富的开发者工具和 SDK,配置与部署简便
由于我们很重视 Supabase 的对接流程,界面部分将交由 Gemini CLI 来生成。它的便利程度简直让人惊喜!公司使用 Google Workspace,真是太好了!
我想用 Supabase 和 Prisma 开发一个备忘录功能。请先帮我创建 page.tsx。
好的,那么请实现这样一个功能:点击 Save Memo 按钮后,将 textarea 中输入的内容以列表形式添加到 Your Memos 中。
外观看起来没问题!输入的内容已经会添加到下方的列表中。不过由于只是通过 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 中编写 schema(数据库结构定义)。
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 ○○ 可以任意命名。
编写完 schema 后,执行迁移(migration)。
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" 是 schema 名称。
POST 函数是向数据库添加数据的 API,通过 create() 方法进行创建。
此外还提供删除、更新等方法,即使不会编写 SQL 语句,也可以凭借 JavaScript 的思路进行操作。
至此,基本准备工作已经完成。
接下来只需将前面编写的 API 复制到 page.tsx 中并完成集成即可。
'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 工具就能管理数据库,非常直观,数据库对接的门槛也降低了!
太好了!
主要从事标记(Markup)工作,并使用 JavaScript、React 和 Next.js 进行前端开发。看到自己参与的网站顺利上线,我会非常开心!爱好是弹吉他。喜欢猫和烤红薯🐱🍠
Hirappi
前端工程师 / 2022年入职