Topics

Connecting to a Database with Supabase × Prisma (Let's Use Gemini CLI, Too!)

  • column

Shō and Hashie, among others, are using Supabase to build some interesting new projects. Unlike the traditional waterfall approach, they're defining functional requirements starting from the UI, and I keep hearing bits and pieces about it. I don't want to fall behind!

So this time, I'll build a simple memo app and write up my notes on how to connect Supabase and Prisma to save data to a database. Gemini CLI is going to be part of this too!

Environment: Next.js

Since we won't be using Supabase's Auth, Realtime, or RLS, and will instead access the database features through an API, we're adopting Prisma. Because it lets you work with database tables like JavaScript or TypeScript objects, it should feel familiar to frontend engineers.

Prisma

Prisma ORM is an ORM (Object-Relational Mapping) library that can be used with TypeScript and JavaScript.

Put simply, it's a tool that lets you operate on a database without writing SQL, using methods defined on programming language objects.

Prisma also automatically generates types from the database schema, so if you try to manipulate a column that doesn't exist or data with a mismatched type, you'll catch it as a compile error!

Supabase

Supabase is a full-stack backend service gaining attention as an open-source alternative to Firebase.

  • High scalability and reliability built on a PostgreSQL database
  • Automatic notifications of data changes through real-time database features
  • Supports a wide range of authentication methods, including email/password, social login, and phone authentication
  • Efficiently manages large files with file storage features
  • Fast development with automatically generated RESTful and GraphQL APIs
  • Rich developer tools and SDKs make setup and adoption simple

Since I want to focus on the steps for connecting to Supabase, I'll have Gemini CLI build the visual parts. It's incredibly handy, almost moving! Good thing our company uses Google Workspace!

I want to build a memo feature with Supabase and Prisma. For now, please create a page.tsx.

Got it. Then please set it up so that when the content entered in the textarea is submitted by clicking the Save Memo button, it gets added as a list under Your Memos.

The layout looks good! Entered content is now added to the list below. However, since this is just handled by a JavaScript event, the list will disappear when the page is reloaded.

To keep the entered content, we'll use a database!

First, create a new project on the Supabase side.

Create it using the "New Project" button. Please make a note of the Database password, as it is required when connecting with Prisma.

Next, we'll integrate Prisma.

npm install prisma --save-dev

npx prisma init

Running these commands will install Prisma and create a prisma folder.

Replace DATABASE_URL in the .env file with the Supabase connection URL. Replace the [YOUR-PASSWORD] part with the Database password you set earlier in Supabase.

DATABASE_URL="postgresql://postgres.odoaingwnxkhkeujwhtp:[YOUR-PASSWORD]@aws-1-ap-northeast-1.pooler.supabase.com:5432/postgres"

Next, write the schema (the definition of the database structure) in /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())
}

You can name the model ○○ as you like.

Once the schema is written, run the migration.

npx prisma migrate dev --name init

A migration.sql file is generated, and the data table is created on the Supabase side.

Creating an API to connect to Supabase

To save the entered values to Supabase, we create an API for the connection.

Install Prisma Client in the project.

npm install @prisma/client

Connecting to 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();
  }
};

The GET function is an API that retrieves all records from the database. The findMany() method fetches all records that match the specified conditions. In Prisma.memo.findMany(), "memo" is the schema name.

The POST function is an API that adds records to the database, using create() to do so.

There are also methods for deleting and updating, so you can work with the data in a JavaScript-like way even if you can't write SQL.

That covers the basic setup.

Next, simply copy the API you wrote earlier into page.tsx and integrate it, and you're all set.

'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>
  );
}

It's saved on the Supabase side, right!

This time I relied on Gemini to put together a rough version, but if we can save values using a database, the range of features and apps we can build will expand too!

I had a preconception that backends would be difficult... but being able to manage the database with a GUI tool made it easy to understand, and the barrier to connecting to a database feels much lower!

Yay!

About the author

I work mainly on markup and frontend development using JavaScript, React, and Next.js. I'm always happy when a site I've been involved in launches successfully! My hobbies are playing the guitar. I love cats and roasted sweet potatoes 🐱🍠

Hirocchi

Frontend Engineer / Joined in 2022

View this staff member's articles

Reliable team structure and responsive support are our strengths

Liberogic's experienced staff actively drive projects forward, which has earned high praise from our clients.
We make sure to properly assign project managers and directors to keep the entire project running smoothly. Our style of allocating resources to the right people, rather than adding full-time commitment unnecessarily, helps prevent cost increases. We are also known for our speed, from understanding the scope of work to preparing and submitting estimates.

Note: Please be advised that we do not actively engage in SES-style on-site staffing assignments.

We support nearly all major project management and chat tools, including Slack, Teams, Redmine, Backlog, Asana, Jira, Notion, Google Workspace, Zoom, and Webex.

Are you struggling with technical challenges or how to approach large-scale projects that involve SES or offshore teams?

Case Studies