2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

前回は、SupabaseとPrismaを導入してデータの永続化(保存)を実装しました。
今回はAPIの開発周りです。
(実は前回すでに実装しており、Supabaseからデータを取得するところはできていました。今回は、もう少し複雑なAPIの部分になります。)

テーブルとSchemaの定義

今回定義したテーブルは次の通りになります。

そもそもテーブル設計などやったことがない僕はこれが良い設計なのか正直わかりませんが、ChatGPTくんに相談しながらこの形になりました。
プロフィール情報に「現住所」と「派遣国」が入るためCountryテーブルを作成してAddressテーブルとDeploymentLocationで使いまわせるようにしました。

次に、Schemaを定義します。前回は仮でSchemaを定義しただけだったので今回は上記のテーブルに合わせて定義します。

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

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

model User {
  id                   Int       @id @default(autoincrement())
  name                 String
  createdAt            DateTime  @default(now())
  image                String?
  currentAddressId     String?
  bio                  String?
  deploymentLocationId String?
  cohortId             String?
  twitterUrl           String?
  instagramUrl         String?
  websiteUrl           String?
  currentAddress       Address?  @relation("CurrentAddress", fields: [currentAddressId], references: [id])
  deploymentLocation   DeploymentLocation? @relation(fields: [deploymentLocationId], references: [id])
  cohort               Cohort?   @relation(fields: [cohortId], references: [id])
  favorites            Favorite[] @relation("UserFavorites")
  favoritedBy          Favorite[] @relation("FavoritedUsers")
}

model Address {
  id                   String   @id @default(uuid())
  countryId            String
  prefectureId         String?
  city                 String?
  otherAddressDetails  String?
  country              Country  @relation(fields: [countryId], references: [id])
  prefecture           Prefecture? @relation(fields: [prefectureId], references: [id])
  users                User[]   @relation("CurrentAddress")
}

model Country {
  id                   String   @id @default(uuid())
  name                 String   @unique
  isDeveloping         Boolean  @default(false)
  addresses            Address[]
  deploymentLocations  DeploymentLocation[]
  prefectures          Prefecture[]
}

model Prefecture {
  id                   String   @id @default(uuid())
  name                 String
  countryId            String
  country              Country  @relation(fields: [countryId], references: [id])
  addresses            Address[]
}

model DeploymentLocation {
  id                   String   @id @default(uuid())
  countryId            String
  specificLocation     String
  country              Country  @relation(fields: [countryId], references: [id])
  users                User[]
}

model Cohort {
  id                   String   @id @default(uuid())
  cohortYear           String
  cohortTerm           String
  users                User[]
}

model Favorite {
  id                   String   @id @default(uuid())
  userId               Int
  favoriteUserId       Int
  user                 User     @relation("UserFavorites", fields: [userId], references: [id])
  favoriteUser         User     @relation("FavoritedUsers", fields: [favoriteUserId], references: [id])

  @@unique([userId, favoriteUserId])
}

Models

データモデルの定義を行います。

model User {
  // 省略
}

Relations

2つのモデルのリレーションを張ります。
UserFavorites

model User {
  favorites            Favorite[] @relation("UserFavorites")
}

favoritesフィールドは、Favoriteモデルとの1対多の関係を定義しています。
@relation("UserFavorites")は、この関係に"UserFavorites"という名前を付けています。

model Favorite {
  id                   String   @id @default(uuid())
  userId               Int
  favoriteUserId       Int
  user                 User     @relation("UserFavorites", fields: [userId], references: [id])
  favoriteUser         User     @relation("FavoritedUsers", fields: [favoriteUserId], references: [id])

  @@unique([userId, favoriteUserId])
}

Favoriteモデルでは、あるユーザーが別のユーザーをお気に入りに追加した関係を表します。

  • id: ユニークな識別子で、UUIDが自動生成されます。
  • userId: お気に入りを追加したユーザーのID。
  • favoriteUserId: お気に入りに追加されたユーザーのID。
  • user: userIdを通じてUserモデルと関連付けられています(お気に入りを追加したユーザー)。
  • favoriteUser: favoriteUserIdを通じてUserモデルと関連付けられています(お気に入りに追加されたユーザー)。
  • @@unique([userId, favoriteUserId]): 同じユーザーペアの重複を防ぐためのユニーク制約。

プロフィールの一覧ページ

/allusers/にアクセスすると一覧ページが見えるようにしようと思います。
前回は実装だと...

app/api/users/route.ts
import { PrismaClient } from '@prisma/client';
import { NextResponse } from 'next/server';

const prisma = new PrismaClient();

export const GET = async () => {
  try {
    await prisma.$connect();
    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/users/を叩くと下記のようなデータが返ってきます。

[
  {
    "id": 1,
    "name": "鈴木花子",
    "createdAt": "2024-09-21T03:34:41.295Z",
    "image": "sample-person.jpg",
    "currentAddressId": "JP002",
    "deploymentLocationId": "DL002",
    "cohortId": "C2020-2",
    "twitterUrl": "https://twitter.com/suzukihanako",
    "instagramUrl": "https://instagram.com/suzukihanako",
    "websiteUrl": "https://suzukihanako.com"
  },
  {
    ...
  },
  {
    ...
  },
]

プロフィールに現住所や派遣国を表示することができないことに気づきました。
なのでAPIを改修する必要があり下記のように対応しました。

const users = await prisma.user.findMany({
  include: {
    currentAddress: {
      include: {
        prefecture: true,
        country: true,
      },
    },
    deploymentLocation: {
      include: {
        country: true,
      },
    },
    cohort: true,
  },
});

includeすると必要なテーブルのデータを取得することができます。

[
  {
    "id": 2,
    "name": "鈴木花子",
    "createdAt": "2024-09-21T03:34:41.295Z",
    "image": "sample-person.jpg",
    "currentAddressId": "JP002",
    "deploymentLocationId": "DL002",
    "cohortId": "C2020-2",
    "twitterUrl": "https://twitter.com/suzukihanako",
    "instagramUrl": "https://instagram.com/suzukihanako",
    "websiteUrl": "https://suzukihanako.com",
    
    "currentAddress": {
      "id": "JP002",
      "countryId": "85",
      "prefectureId": "14",
      "city": "横浜市",
      "otherAddressDetails": null,
      "prefecture": {
        "id": "14",
        "name": "神奈川県",
        "countryId": "1"
      },
      "country": {
        "id": "85",
        "name": "日本",
        "isDeveloping": false
      }
    },
    
    "deploymentLocation": {
      "id": "DL002",
      "countryId": "78",
      "specificLocation": "ジャカルタ",
      "country": {
        "id": "78",
        "name": "インドネシア",
        "isDeveloping": true
      }
    },
    
    "cohort": {
      "id": "C2020-2",
      "cohortYear": "2020",
      "cohortTerm": "2"
    }
  },
  {
    ...
  },
  {
    ...
  }
]

もっと整理できそうな気がするけど、一旦これで進めます...。

ProfileCard.ts
import Link from 'next/link';
import { UserType } from '@/app/type';
import Image from 'next/image';

export default function ProfileCard(user: UserType) {
  const { id, name, image, currentAddress } = user;
  return (
    <Link href={`/${id}`} passHref className="mb-4 block rounded-lg bg-white p-6 shadow-md">
      <div className="flex items-center">
        <Image
          width={200}
          height={200}
          src={`http://localhost:3000/images/${image}`}
          alt={name}
          className="mr-4 size-16 rounded-full object-cover"
        />
        <div>
          <h2 className="text-xl font-bold">{name}</h2>
          <p className="text-gray-600">
            {currentAddress.country.name} {currentAddress.prefecture?.name}
          </p>
        </div>
      </div>
      <p className="mt-4 text-gray-800">コメントが入ります</p>
    </Link>
  );
}

これで都道府県を表示することができるようになりました。
CleanShot 2024-09-22 at 07.01.07@2x.jpg

一旦Prismaを使ってDBからデータを取得して表示させるところはこのへんにしておきます。
他のページのAPIも作っていきます🫶🏿

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?