前回は、SupabaseとPrismaを導入してデータの永続化(保存)を実装しました。
今回はAPIの開発周りです。
(実は前回すでに実装しており、Supabaseからデータを取得するところはできていました。今回は、もう少し複雑なAPIの部分になります。)
テーブルとSchemaの定義
今回定義したテーブルは次の通りになります。
そもそもテーブル設計などやったことがない僕はこれが良い設計なのか正直わかりませんが、ChatGPTくんに相談しながらこの形になりました。
プロフィール情報に「現住所」と「派遣国」が入るためCountry
テーブルを作成してAddress
テーブルとDeploymentLocation
で使いまわせるようにしました。
次に、Schemaを定義します。前回は仮でSchemaを定義しただけだったので今回は上記のテーブルに合わせて定義します。
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/
にアクセスすると一覧ページが見えるようにしようと思います。
前回は実装だと...
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"
}
},
{
...
},
{
...
}
]
もっと整理できそうな気がするけど、一旦これで進めます...。
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>
);
}
一旦Prismaを使ってDBからデータを取得して表示させるところはこのへんにしておきます。
他のページのAPIも作っていきます🫶🏿