Topics

使用 Supabase × Prisma 連接資料庫(還要用 Gemini CLI 喔!)

  • column

翔、Hasshy 等人正在利用 Supabase 進行一些很有意思的開發。與過去的瀑布式開發不同,聽說是從 UI 開始一路定義功能需求,雖然聽到的細節還不太清楚,但我也不能輸!

於是,這次我們將製作一個簡單的備忘錄應用程式,並整理如何串接 Supabase 與 Prisma 將資料儲存到資料庫的步驟,作為備忘紀錄。Gemini CLI也要一起用上喔!

環境:Next.js

我們不使用 Supabase 的 Auth、Realtime 與 RLS,而是透過 API 使用資料庫功能,因此採用 Prisma。由於可以像操作 JavaScript / TypeScript 物件一樣處理資料表,應該能以前端工程師熟悉的寫法來撰寫程式。

Prisma

Prisma ORM 是可於 TypeScript 與 JavaScript 中使用的 ORM(Object-Relational Mapping)函式庫。

簡單來說,這是一種工具,能透過程式語言物件定義的方法,無需撰寫 SQL 即可操作資料庫。

此外,Prisma 會根據資料庫的結構自動產生型別,因此在嘗試操作資料庫中不存在的欄位或型別不符的資料時,會以編譯錯誤的形式讓你及早發現!

Supabase

Supabase 是一套備受關注的全端後端服務,可作為開源的 Firebase 替代方案。

  • 以 PostgreSQL 資料庫為基礎,具備高度擴充性與可靠性
  • 透過即時資料庫功能,資料變更可自動通知
  • 支援多種驗證方式(電子郵件/密碼、社群登入、電話驗證等)
  • 透過檔案儲存功能,有效管理大容量檔案
  • 自動生成 RESTful API 與 GraphQL API,實現快速開發
  • 提供豐富的開發者工具與 SDK,設定與導入都十分簡便

由於重視與 Supabase 的串接流程,我們將使用 Gemini CLI 來製作介面部分。這真的方便到令人感動!公司使用 Google Workspace,real 太好了!

我想用 Supabase 與 Prisma 製作備忘錄功能。請先建立 page.tsx。

好的,那麼請設計成:將 textarea 中輸入的內容,在按下 Save Memo 按鈕後,以清單形式新增至 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 進行前端開發。若我參與的網站能順利上線,我會非常開心!興趣是彈吉他。喜歡貓咪和烤地瓜🐱🍠

Hirattch

前端工程師 / 2022 年入職

查看此員工的文章

我們的優勢在於穩健的團隊體制與快速的應對能力

Liberogic 由經驗豐富的資深人員積極推動專案,因此獲得客戶的高度評價。
我們會妥善指派專案經理與總監,致力於順利推進整體專案。 我們採取依據專案需求適才適所分配資源的方式,避免因全程投入而增加不必要的成本,並且在掌握業務內容、製作與提交估價方面的速度也廣受好評。

注: 本公司不積極承接類似 SES 的駐點業務等,敬請事先諒解。

可搭配 Slack、Teams、Redmine、Backlog、Asana、Jira、Notion、Google Workspace、Zoom、Webex 等幾乎所有主要的專案管理工具與聊天工具使用。

在利用 SES 或離岸外包的大型專案中,是否對技術挑戰與推進方式感到困擾?

案例分析