【個人開発_#4】SupabaseとPrismaを導入してデータの永続化

前回は、Googleログインを実装しました。
今回は、SupabaseとPrismaを導入してデータの永続化（保存）ができるようにしていきます。

## Supabaseの導入
### Supabaseとは？
- オープンソースのリアルタイムデータベース
- バックエンドの機能を提供するプラットフォーム
- PostgreSQLデータベースがベース
- 開発者はサーバーサイドのコードを書かずにバックエンドの機能をそのえたアプリケーションの構築が可能


### Supabaseの導入
公式サイトで登録を行います。

https://supabase.com/

## Prismaのインストール
PrismaはNode.jsのORMの１つです。

https://www.prisma.io/

公式ドキュメントに沿ってインストールを行います。

https://www.prisma.io/docs/getting-started/quickstart

```
$ pnpm install prisma --save-dev
```

:::note
Dockerのコンテナ内でインストールを実行するために...
- コンテナ内にアクセスします
`docker-compose exec app sh`
- prismaをインストールします
`pnpm add prisma --save-dev`
:::

実際に、Next.jsからデータを追加・削除するにはPrisma　Clientが必要になるのでインストールします。

```
pnpm install @prisma/client
```
https://www.npmjs.com/package/@prisma/client

Prismaを初期化します。
```
npx prisma init
```
Next.jsのプロジェクトにprismaフォルダが追加されて`schema.prisma`ファイルが作られます。
![CleanShot 2024-07-17 at 08.33.54@2x.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/592040/44d68ead-6b89-b9aa-6e86-492f5d24abc2.jpeg)

```javascript: schema.prisma
// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema

// Looking for ways to speed up your queries, or scale easily with your serverless or edge functions?
// Try Prisma Accelerate: https://pris.ly/cli/accelerate-init

generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}
```
`.env`ファイルにDATABASE_URLが追加されます。
```
DATABASE_URL="postgresql://johndoe:randompassword@localhost:5432/mydb?schema=public"
```
ここをSupabase作成時のデータベースのURLに変更します。

次に、スキーマを設定します。まずは、最低限のスキーマを登録してSupabaseにテーブルを作成できるかを試してみます。

```javascript:schema.prisma
model User {
  id Int @id @default(autoincrement())
  name String
  date DateTime @default(now())
  image String
  currentAddress String
  bio String
}
```
スキーマの設定後、マイグレーションを実行してSupabaseにテーブルを作成します。
```bash
$ npx prisma init

✔ Your Prisma schema was created at prisma/schema.prisma
  You can now open it in your favorite editor.

warn Prisma would have added DATABASE_URL but it already exists in .env
warn You already have a .gitignore file. Don't forget to add `.env` in it to not commit any private information.

Next steps:
1. Set the DATABASE_URL in the .env file to point to your existing database. If your database has no tables yet, read https://pris.ly/d/getting-started
2. Set the provider of the datasource block in schema.prisma to match your database: postgresql, mysql, sqlite, sqlserver, mongodb or cockroachdb.
3. Run prisma db pull to turn your database schema into a Prisma schema.
4. Run prisma generate to generate the Prisma Client. You can then start querying your database.
5. Tip: Explore how you can extend the ORM with scalable connection pooling, global caching, and real-time database events. Read: https://pris.ly/beyond-the-orm

More information in our documentation:
https://pris.ly/d/getting-started
```

マイグレーションを実行します。
```bash
npx prisma migrate dev --name init
```
prismaディレクトリ配下にmigrationsディレクトリが作成されます。
![CleanShot 2024-07-24 at 06.33.27@2x.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/592040/5b54cfa1-fcee-e340-e481-dad2821401db.jpeg)

Supabaseを見るとテーブルができたのがわかります。
![CleanShot 2024-07-17 at 09.28.22@2x.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/592040/cd4814b7-99c2-02e3-a0e9-726d7329a0e2.jpeg)
データをGUIから適当に入力しておきます。

APIでエンドポイントを作成
appディレクトリ配下にapi/usersフォルダを作成して、route.tsを作成します。
データベースを操作するために、prismaのインスタンスを作成します。

```typescript
import { PrismaClient } from '@prisma/client';
import { NextResponse } from 'next/server';

const prisma = new PrismaClient();

export async function main() {
  try {
    await prisma.$connect();
  } catch (error) {
    return Error('Failed to connect to database');
  }
}

export const GET = async (req: Request, res: Response) => {
  try {
    await main();
    const users = await prisma.user.findMany();
    return NextResponse.json(users, { status: 200 });
  } catch (err) {
    return NextResponse.json({ message: 'Server error', err }, { status: 500 });
  } finally {
    await prisma.$disconnect();
  }
};
```
これでAPIの作成は完了です。
APIを確認するツール（今回はVSCodeの拡張機能Thunder Client）で確認してみます。
![CleanShot 2024-07-24 at 06.40.52@2x.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/592040/f4d24d80-8de9-8dc6-6c0c-d682b468b66b.jpeg)
無事にデータが取得できていそうです。

次にこちらのデータをフロント側で表示してみようと思います。
一旦取得できたデータを全て表示したいので、appディレクトリ配下にallusersディレクトリを作成して今回の最低限のデータ取得の型を定義しておきます。
```typescript:app/allusers/page.tsx
import Footer from '@/components/Footer';
import Header from '@/components/Header';
import ProfileCard from '@/components/ProfileCard';
import Image from 'next/image';
import { UserType } from '@/app/type';

async function fetchAllUsers() {
  const res = await fetch('http://localhost:3000/api/users', {
    cache: 'no-cache',
  });

  return res.json();
}

export default async function Page() {
  const users: UserType[] = await fetchAllUsers();

  return (
    <>
      <Header />
      <div className="px-4">
        <div className="flex items-center text-2xl">
          <Image src="/images/icon-member.svg" alt="" width={40} height={40} />
        </div>

        <div className="mt-10">
          {users.map((user: UserType, index: number) => (
            <ProfileCard key={index} {...user} />
          ))}
        </div>
      </div>
      <Footer />
    </>
  );
}
```
```app/type.d.ts
export interface UserType {
  name: string;
  location: string;
  image: string;
  bio: string;
}
```
```components/ProfileCard/index.tsx
import Link from 'next/link';
import { UserType } from '@/app/type';

export default function ProfileCard({ name, location, image, bio }: UserType) {
  return (
    <Link href="#" className="block bg-white rounded-lg shadow-md p-6 mb-4">
      <div className="flex items-center">
        <img src={image} alt={name} className="w-16 h-16 object-cover rounded-full mr-4" />
        <div>
          <h2 className="text-xl font-bold">{name}</h2>
          <p className="text-gray-600">{location}</p>
        </div>
      </div>
      <p className="mt-4 text-gray-800">{bio}</p>
    </Link>
  );
}
```

![CleanShot 2024-07-24 at 06.49.43@2x.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/592040/35acc063-90dd-2d2f-2075-9080da1f3f2d.jpeg)

無事に表示表示できました！🎉
とりあえず、この後は全て記事にするのはしませんが、必要なAPIを開発していきます！✌️