翔和 hassy 他们在使用 Supabase 做一些有趣的开发工作。与以往的瀑布式开发不同,我听说 UI 方面在进行功能需求定义。我也不能落后啊!
因此,这次我将创建一个简单的备忘录应用,并整理 Supabase 和 Prisma 联动、将数据保存到数据库的步骤作为备忘录。Gemini CLI 也用上了呢!
环境:Next.js
Supabase 的 Auth、Realtime 和 RLS 不使用,为了从 API 利用数据库功能而采用 Prisma。因为数据库表可以像 JavaScript/TypeScript 对象一样处理,所以前端工程师应该能写出熟悉的代码。
Prisma
Prisma ORM 是一个可在 TypeScript 和 JavaScript 中使用的 ORM(对象关系映射)库。
简单来说,就是用编程语言中定义的对象方法,无需编写 SQL 即可操作数据库的工具。
而且 Prisma 可以从数据库模式自动生成类型,所以如果尝试操作数据库中不存在的列或类型不匹配的数据,就能通过编译错误发现问题!
Supabase
Supabase 是一个开源的 Firebase 替代方案,作为全栈后端服务备受关注。
- 基于 PostgreSQL 数据库的高扩展性和可靠性
- 实时数据库功能,可自动通知数据变更
- 支持多种身份验证方法(电子邮件/密码、社交登录、电话验证等)
- 文件存储功能,可高效管理大容量文件
- 自动生成 RESTful API 和 GraphQL API,加速开发
- 提供丰富的开发者工具和 SDK,配置和集成简便
考虑到与 Supabase 集成的步骤很重要,我们希望用 Gemini CLI 来创建前端界面。这真的是超级好用的工具!还好公司用 Google Workspace!
我想用 Supabase 和 Prisma 构建备忘录功能。先麻烦创建一下 page.tsx。
好的,那就让 textarea 输入的内容在点击 Save Memo 按钮时作为列表项添加到 Your Memos 中吧。
外观就像这样就可以了!输入的内容会被添加到下面的列表中。但这只是用 JavaScript 处理事件,所以重新加载页面后列表就会消失。
我们使用数据库来保持输入的内容!
首先在 Supabase 端创建一个新项目。
通过"New Project"按钮创建,数据库密码在使用 Prisma 连接时需要用到,请妥善保管。
接下来集成 Prisma。
npm install prisma --save-dev
npx prisma init
输入这些命令后,Prisma 会被安装,并出现 Prisma 文件夹。
将 .env 文件中的 DATABASE_URL 替换为 Supabase 的连接 URL。将 [YOUR-PASSWORD] 部分替换为您之前在 Supabase 中设置的数据库密码。
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 ○○ 可以任意命名。
编写 schema 后,我们将执行迁移。
npx prisma migrate dev --name init
将生成 migration.sql 文件,并在 Supabase 端创建数据表。
创建用于连接 Supabase 的 API
创建连接 API,以便将输入的值保存到 Supabase。
在项目中安装 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 一样进行操作。
到此为止,所有准备工作都已完成。
接下来只需将之前编写的 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 工具就能管理数据库,理解起来也很清楚,数据库连接的门槛降低了!
成功了!
我主要从事以标记语言为中心,使用JavaScript、React、Next.js进行前端开发的工作。当我参与的网站顺利发布时,我会感到很高兴!我的爱好是弹吉他。喜欢猫和烤红薯🐱🍠
Hiratch
前端工程师 / 2022年入职