영속성이란 데이터를 생성한 프로그램이 종료되어도, "데이터는" 사라지지 않는 데이터의 특성을 말한다.
ORM은 DataBase와 Web Aplication의 연결을 객체지향적
으로 도와주는 역할을 한다.!
ORM(Object Relational Mapping)
은 객체로 연결해준다라는 의미로, 어플리케이션과 데이터베이스를 연결할 때, SQL 언어가 아닌, 어플리케이션 개발언어로 데이터베이스를 접근할 수 있게 해주는 툴이다.
ORM은 SQL 문법 대신 어플리케이션의 개발언어를 그대로 사용할 수 있게 하므로, 개발 언어의 일관성과 가독성을 높여준다는 장점이 있다.
👉 쉽게 말하자면,
DB (SQL 언어) ------ ORM Tools -------> 개발 언어 (Java, JS, JS, C 등)
이게 무슨 말인가 하면, ORM은 도구(Tools)로 언어에 따른 여러 종류가 있는데, 아래서 설명할 Prisma
를 사용한 이유는 내가 개발한 어플리케이션의 환경이 TS이기 때문이다.
Prisma는 데이터베이스 작업을 간단하게 만들어주는 오픈 소스 ORM(Object-Relational Mapper)이다.
다른 ORM과는 다른 Prisma의 특징은 무엇일까?
migrate
)을 제공한다. 👉 이를 통해 항상 웹 애플리케이션과 데이터데이스를 호환할 수 있다. generate
라는 강력한 쿼리 빌더가 포함되어 있어 👉 오류 방지에 도움된다.studio
명령어를 통해 웹 브라우저에서 가시적으로 데이터를 테이블 형태로 보고 편집할 수 있는 UI를 제공한다. 👉 덕분에 코드를 작성하지 않고도 명시적으로 데이터를 확인할 수 있다.npm install prisma @prisma/client
npx prisma init
npm install prisma typescript ts-node @types/node --save-dev
# 만약 prisma 디렉터리를 src 하위로 옮긴다면, package.json에 아래 내용을 추가
"prisma": {
"schema": "src/prisma/schema.prisma"
}
DATABASE_URL="postgresql://username:password@hostname:5432/dbName?schema=schemaName"
import { PrismaClient } from "@prisma/client";
const prismaClient = new PrismaClient();
prisma.schema
파일 안에 작성model User {
id Int @id @default(autoincrement())
name String
createdAt DateTime @default(now()) }
npx prisma migrate dev
prisma.schema
에 작성한 model
의 타입을 만들어주는 역할(제너레이터) 👉 코드 안전성 부여 npx prisma generate
npx prisma studio
// & prisma studio와 현재 개발 서버를 동시에 띄워야 한다.
npm run dev
일대일(1-1
) 관계에서는 참조할 테이블의 키를 외래키(FK)로 가져온다.
One-To-many
의 관계일 경우, many
에 one
의 정보를 column으로 추가한다.다수-다수
관계일 때에는 중간에 중계 테이블을 만든다. (이때 통상적으로 양측 테이블의 이름을 합쳐 camelCase로 짓는다.)
그리고 각 테이블은 새로 만든 중계 테이블과 join
해 정보를 가져온다.
📦 src
┣ 📂 contexts
┃ ┣ 📂 users
┃ ┃ ┣ 📜 users.controller.ts
┃ ┃ ┗ 📜 users.service.ts
┃ ┗ 📜 index.ts
┣ 📂 prisma
┃ ┣ 📂 migrations
┃ ┣ 📜 client.prisma.ts
┃ ┗ 📜 schema.prisma
┗ 📜 app.ts
📜 .env
import bodyParser from "body-parser";
import express from "express";
import controllers from "./contexts";
const app = express();
const port = 포트번호;
const jsonParser = bodyParser.json();
app.use(jsonParser);
app.use(controllers);
app.listen(port, () => {
console.log("**----------------------------------**");
console.log(" ========== Server is On!!! ========= ");
console.log("**----------------------------------**");
});
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model User {
id Int @id @default(autoincrement())
email String @unique
encryptedPassword String
profile UserProfile?
createdAt DateTime @default(now())
}
model UserProfile {
id Int @id @default(autoincrement())
nickname String?
name String?
gender String?
age Int?
phoneNumber String?
address String?
user User @relation(fields: [userId], references: [id])
userId Int @unique
}
import { PrismaClient } from "@prisma/client";
const prismaClient = new PrismaClient();
export default prismaClient;
❗️TIP 1
서버에서 에러가 뜨면(400, 404 등) 서버가 꺼지게 된다. 그럼 매번 재시동을 해줘야 하는데, 이문제를 해결하는 방법이 있다.
👉 세번째 인자인next()
를 사용하면 된다.
❗️TIP 2
res: Response<Omit<User, "encryptedPassword">>
안에 omit을 여러개 사용하고 싶다면 👉|
를 사용하면 된다.
import { User } from "@prisma/client";
import { hash } from "bcrypt";
import { NextFunction, Request, Response } from "express";
import prismaClient from "../../prisma/client.prisma";
interface userInfoType = {
email: string;
password: string;
nickname: string;
name: string;
gender: string;
age: number;
}
const createUser = async(
req:Request<never, unknown, userInfoType>,
res: Response<Omit <User, "encryptedPassword>>,
next : NextFunction
) => {
try {
const { email, password, nickname, name, gender, age } = req.body;
if (!email.trim()) throw new Error("No email");
if (!password.trim()) throw new Error("No password");
// 요청헤더로 받은 password 해싱
const encryptedPassword = await hash(password, 12);
const user = await prismaClient.user.create({
data : {
email,
encryptedPassword,
profile: {
create: {
nickname, name, gender, age
}
}
}
});
res.json(user);
} catch (e) {
next(e)
}
};
const getUsers = async (_: Request, res: Response) => {
const users = await prismaClient.user.findMany();
res.json(users);
}
const getUser = async (
req: Request<{ userId : string }>,
res: Response
) => {
const parsedUserId = Number(req.params.userId);
if (isNaN(parsedUserId)) throw new Error("error");
const user = await prismaClient.user.findUnique({
where : { id : parsedUserId }.
select : {
id : true,
email : true,
created : true,
profile: true,
},
include : {
//! SQL의 JOIN 역할 👉 만약 특정 cols만 가져오고 싶다면 안에 객체를 넣으면 됨
profile: true
}
});
res.json(user);
}
const UpdateUser = async(
req: Reqeust<{ userId : string },
Omit<User, "encryptedPassword">,
{ email : string }>,
res: Response
) => {
const parsedUserId = Number(req.params.userId);
if (isNaN(parsedUserId)) throw new Error("error");
const { email } = req.body;
const user = await prismaClient.user.update({
where : { id : parsedUserId },
data : { email },
select : {
id : true, email : true, createdAt : true
} // 특정 cols만 가져올 것
});
if (!user) throw new Error("유저 없음");
res.json(user);
}
const deleteUser = async(
req: Request<{ userId : string }>,
res : Response
) => {
const parsedUserId = Number(req.params.userId);
if (isNaN(parsedUserId)) throw new Error("error");
const user = await prismaClient.user.delete({
where : { id : parsedUserId }
})
res.json(user);
}
const usersService = {
createUser,
getUsers,
getUser,
updateUser,
deleteUser
}
export default usersService;
import { Router } from "express";
import usersService from "./users.service";
const usersController = Router();
usersController.post("/", usersService.createUser);
usersController.get("/", usersService.getUsers);
usersController.get("/:userId", usersService.getUser);
usersController.put("/:userId", usersService.updateUser);
usersController.delete("/:userId", usersService.deleteUser);
export default usersController;
import { Router } from "express";
import usersController from "./users/users.controller";
const controllers = Router();
controllers.use("/users", usersController);
export default controllers;
📂 출처
✨ NextJS + TypScript + Prisma