Product Model
상품에 대한 정보를 찾아와야 합니다. 따라서 앞으로는 같은 과정을 거칠겁니다. 일단 model을 만들고 -> 데이터베이스를 수정하고 -> mutation을 한 다음 -> 데이터를 가져올 겁니다. ( by useSWR )
이것을 하기 전에 Prisma Client가 여러번 생성되는 것을 막기 위하여 전역으로 client를 만들고 만약에 있다면 다시 생성하지 않는 방법을 채택하여, 여러번 instance가 생성되지 않게 해보겠습니다.
/lib/client/client.ts
import { PrismaClient } from "@prisma/client";
declare global {
var client: PrismaClient | undefined;
}
const client = global.client || new PrismaClient();
// development라면 global을 나중에 설정해 주어야 한다.
// https://www.prisma.io/docs/support/help-articles/nextjs-prisma-client-dev-practices
if (process.env.NODE_ENV !== "production")
global.client = client;
export default client;
만일 Typescript로 개발하던 중에 아주 잘 작성된 JS모듈을 가져와서 쓰고자 한다면, 해당 모듈 내의 함수는 typescript로 정의되어 있지 않기 때문에 컴파일 과정에서 오류가 뜬다. 모듈이 해당 속성을 가지고 있음에도 type이 정해져 있지 않아서 가지고 있다고 해석하는 것이다.
위와 같이 decalre키워드를 써서 정의해두면 type이 인식되어 typescript compiler가 해석할 수 있게 된다.
간단히 말해서 컴파일러에게 "이건 이미 존재하고 다른 코드에서 참조할 수 있다."라고 설명하는 것입니다.
/prisma/schema.prisma
// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema
generator client {
provider = "prisma-client-js"
previewFeatures = ["referentialIntegrity"]
}
datasource db {
provider = "mysql"
url = env("DATABASE_URL")
referentialIntegrity = "prisma"
}
model User {
id Int @id @default(autoincrement())
phone String? @unique
email String? @unique
name String
avatar String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
tokens Token[]
products Product[]
}
model Token {
id Int @id @default(autoincrement())
payload String @unique
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
userId Int
}
model Product {
id Int @id @default(autoincrement())
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
userId Int
image String
name String
price Int
description String @db.MediumText
}
다음과 같이 Product를 위한 Prisma Model을 추가해 주었습니다. 그리고 npx prisma db push를 해주어 schema를 추가 해 주도록 하겠습니다.
이제 반복 작업을 진행해 보도록 하겠습니다.
/pages/products/upload.tsx
import type { NextPage } from "next";
import Layout from "@components/layout";
import Button from "@components/button";
import Input from "@components/Input";
import TextArea from "@components/textArea";
import { useForm } from "react-hook-form";
import useMutation from "@libs/client/useMutation";
import { useEffect } from "react";
import { Product } from "@prisma/client";
import { useRouter } from "next/router";
interface UploadProductForm {
name: string;
price: number;
description: string;
}
interface UploadProductMutation {
ok: boolean;
product: Product;
}
const Upload: NextPage = () => {
const router = useRouter();
const { register, handleSubmit } =
useForm<UploadProductForm>();
const [uploadProduct, { loading, data }] =
useMutation<UploadProductMutation>("/api/products");
const onValid = (data: UploadProductForm) => {
if (loading) return;
uploadProduct(data);
};
useEffect(() => {
if (data?.ok) {
router.push(`/products/${data.product.id}`);
}
}, [data, router]);
return (
<Layout canGoBack>
<form
className="space-y-5 px-4 py-3"
onSubmit={handleSubmit(onValid)}
>
<div>
<div>
<label className="flex h-48 w-full cursor-pointer items-center justify-center rounded-md border-2 border-dashed border-gray-300 py-6 text-gray-600 hover:border-orange-500 hover:text-orange-500">
<svg
className="h-12 w-12"
stroke="currentColor"
fill="none"
viewBox="0 0 48 48"
aria-hidden="true"
>
<path
d="M28 8H12a4 4 0 00-4 4v20m32-12v8m0 0v8a4 4 0 01-4 4H12a4 4 0 01-4-4v-4m32-4l-3.172-3.172a4 4 0 00-5.656 0L28 28M8 32l9.172-9.172a4 4 0 015.656 0L28 28m0 0l4 4m4-24h8m-4-4v8m-12 4h.02"
strokeWidth={2}
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
<input className="hidden" type="file" />
</label>
</div>
<Input
register={register("name", { required: true })}
required
label="Name"
name="name"
type="text"
/>
<Input
register={register("price", { required: true })}
required
label="Price"
name="price"
kind="price"
type="number"
/>
<TextArea
register={register("description", {
required: true,
})}
name="description"
label="Description"
required
/>
<Button
text={loading ? "Loading ..." : "Upload item"}
/>
</div>
</form>
</Layout>
);
};
export default Upload;
enter page에서 한 것과 같게, 'react-hook-form'을 사용해서 Input, TextArea 컴포넌트를 수정해 줍니다.
/components/textArea.tsx
import type { UseFormRegisterReturn } from "react-hook-form";
interface TextAreaProps {
name?: string;
label?: string;
register: UseFormRegisterReturn;
[key: string]: any;
}
const TextArea = ({
name,
label,
register,
...rest
}: TextAreaProps) => {
return (
<div>
{label ? (
<label
htmlFor={name}
className="mb-1 block text-sm font-medium text-gray-700"
>
{label}
</label>
) : null}
<textarea
{...register}
id={name}
{...rest}
rows={4}
className="focus:border-1 mt-1 w-full rounded-md border-gray-300 shadow-sm focus:border-orange-500 focus:ring-1 focus:ring-orange-500"
/>
</div>
);
};
export default TextArea;
이제 폼을 다 작성하고 제출을 useMutation을 통해서 할 것인데, 어디로 api요청을 보낼건지 정하고 이를 작성해 주어야 합니다. 저희는 '/api/products'로 보냅니다.
/pages/api/products/index.ts
import client from "@libs/client/client";
import withHandler, {
ResponseType,
} from "@libs/server/withHandler";
// prettier-ignore
import type { NextApiRequest, NextApiResponse, NextApiHandler } from "next";
import { withApiSession } from "@libs/server/withSession";
const handler: NextApiHandler = async (
req: NextApiRequest,
res: NextApiResponse
) => {
const {
body: { name, price, description },
session: { user },
} = req;
const product = await client.product.create({
data: {
name,
price: +price,
description,
image: "xx",
user: {
connect: {
id: user?.id,
},
},
},
});
res.json({
ok: true,
product,
});
};
export default withApiSession(
withHandler({
method: "POST",
handler,
})
);
다음과 같이 프론트에서 받은 name, price, description을 받아서 Prisma Client를 사용해서 session에 저장되어 있는데 현재 유저와 connect하여 db record를 Product테이블에 적절하게 조합하여 추가해 주는 작업을 거칩니다. 그리고 반환은 {ok: boolean, product: Product }형식으로 합니다. 그 이유는 ok가 되면 프론트에서 next router를 통해서 추가한 product의 page로 이동해 주고 싶기 때문입니다.
여기서 팁도 있는데, Prisma Client에서 저희가 만든 Product의 타입을 이미 알고 있어서 이를 타입으로 활용할 수 있다는 점입니다. upload.tsx를 보면 UploadProductMutation Interface를 보면 product의 리턴 타입이 @prisma/client에서 참조한 Product타입입니다.
'Web > CloneCoding' 카테고리의 다른 글
[ Carrot Market ] #12 - Community - 1 (0) | 2022.05.31 |
---|---|
[Carrot Market] #11 - PRODUCT - FINISH (0) | 2022.05.18 |
[Carrot Market] #10 - AUTHORIZATION (0) | 2022.05.13 |
[Carrot Market] #9 - Authentication - 2 (0) | 2022.05.12 |
[Carrot Market] #9 - Authentication - 1 (0) | 2022.05.12 |