Prisma 에는 Mongoose 와 동일하게 findMany(), findFisrt() 등 다양한 메서드를 지원한다.
이러한 메서드들을 통해 데이터를 조작하는데, Mongoose 에서는 schema를 이용해 DB를 조작했다면, Prisma에서는 Prisma Client를 이용해 MySQL 데이터를 조작하는 것의 차이이다.
이전에 생성한 Posts 테이블의 구조를 보면
다음과 같이 작성되어 있다.
데이터를 직접 받아와야 하는 컬럼은 title(게시글 제목), content(내용), password(비밀번호) 로 총 3개이다.
그 외의 컬럼 postId, createdAt, updatedAt 은 아무 데이터를 입력받지 않아도 기본값을 가질 수 있도록 구성했다.
이 테이블을 통해 진행할 실습은 게시글 관련 API들을 만들고 이에 필요한 인자값 3개를 이용해 권한 검증 및 데이터 생성 과정을 구현할 것이다.
// routes/posts.router.js
import express from 'express';
import { PrismaClient } from '@prisma/client';
const router = express.Router(); // express.Router()를 이용해 라우터를 생성합니다.
const prisma = new PrismaClient({
// Prisma를 이용해 데이터베이스를 접근할 때, SQL을 출력해줍니다.
log: ['query', 'info', 'warn', 'error'],
// 에러 메시지를 평문이 아닌, 개발자가 읽기 쉬운 형태로 출력해줍니다.
errorFormat: 'pretty',
}); // PrismaClient 인스턴스를 생성합니다.
export default router;
routes/post.router.js 파일을 생성하고, express 프로젝트 초기화를 진행한다.
// app.js
import express from 'express';
import PostsRouter from './routes/posts.router.js';
const app = express();
const PORT = 3017;
app.use(express.json());
app.use('/api', [PostsRouter]);
app.listen(PORT, () => {
console.log(PORT, '포트로 서버가 열렸어요!');
});
이 두개의 과정으로 기본적인 서버 구동과 express 라이브러리를 통한 프로젝트 초기화가 진행이 된다.
그럼 이제 API를 생성하기 전에, 명세서부터 확인하고 들어가자.
우리가 구현할 API가 어떤 메서드를 사용하고, 어떤 request를 받고 어떤 response 를 보내야 하는지 확인하는 것은 API 구현 과정에 있어서 무엇보다 중요한 절차이다.
API 명세서를 보면 POST 메서드를 사용하고 URL은 '/api/posts' 로 구성한다. 또한 title, content, password 를 request로 받아 data : { postId, title, content, password, createdAt, updatedAt } 을 response 로 보내줘야 한다.
전체적인 로직은 다음과 같다.
1. title, content, password 를 body에 담아 전달하면
2. 해당 값들을 받아 Posts 테이블에 데이터 추가
3. API를 생성된 게시글 반환
router.post('/posts', async (req, res, next) => {
const { title, content, password } = req.body;
const post = await prisma.posts.create({
data: {
title: title,
content: content,
password: password,
}
});
return res.status(201).json({ data: post });
});
다음과 같이 구현할 수 있다.
request 로 전달하는 body 데이터를 구조분해 할당을 통해 각각 저장하고, 이를 Create 메서드를 통해 posts 테이블에 새롭게 데이터를 추가한다.
이후 res.status 를 통해 해당 post 데이터를 반환하는 과정이다.
테스트 시행 결과 다음과 같이 나타난다.
title, content, password 총 3개의 컬럼에 대한 값만 입력해주었지만, postId, createdAt, updatedAt 의 컬럼은 자동적으로 데이터가 입력되는 모습 또한 확인할 수 있다.
API 명세서를 확인하는 방법은 위에서 언급했으니 이번에는 바로 코드로 넘어가본다.
게시글 목록/상세 조회 API는 동일하게 GET 메서드를 사용해 같이 적었지만, 서로 다른 목적이므로 API 구현을 분리해주어야 한다.
먼저 게시글 목록 조회 API이다.
// 게시글 목록 조회 API
router.get('/posts', async (req, res, next) => {
const posts = await prisma.posts.findMany({
// PW, Content 가 조회되면 안됨
select: {
postId: true,
title: true,
createdAt: true,
updatedAt: true
}
});
return res.status(200).json({ data: posts });
});
게시글의 목록만 전체적으로 조회하면 되므로 특정 body 데이터 등이 필요가 없다.
또한 findMany 를 통해 posts 테이블의 전체 데이터를 select 를 거쳐 받아왔다.
테스트를 위해 게시글 생성 API를 총 3번 시행하였고, 목록 조회 결과 다음과 같이 원하는 데이터만 보여주는 전체 게시글의 목록이 조회되었다.
다음은 게시글 상세 조회 API이다.
상세 조회라고 한다면, 특정 게시글을 집어서 해당 게시글의 데이터를 전부 보여주어야 할 것이다.
이를 위해선 어떤 게시글을 보려고 하는지에 대한 정보를 받아야 하는데, 이는 URL 에 정보를 담아 보내는 params 를 통해 받아오도록 한다.
// 게시글 상세 조회 API
router.get('/posts/:postId', async (req, res, next) => {
const { postId } = req.params;
const post = await prisma.posts.findFirst({
where: {
postId: parseInt(postId),
},
select: {
postId: true,
title: true,
content: true,
createdAt: true,
updatedAt: true,
}
});
return res.status(200).json({ data: post });
});
말한대로 어떤 게시글을 보려고 하는지를 정하는 postId 는 req.params 를 통해 저장해주었다.
findFirst 메서드를 통해 첫번째 데이터를 받아오는데, posts 테이블에서 where 조건을 통해 해당 postId 와 일치하는 데이터를 특정해주었다.
이 where 조건을 보면 postId : parseInt(postId) 에서 parseInt 라는 함수를 활용해주는데, 이는 받아온 데이터를 Int 타입으로 변환하기 위해서 사용한다.
parmas로 받아온 데이터들은 기본적으로 string 타입이다. 하지만 우리가 where 조건으로 찾는 postId 의 경우 Int 타입으로 선언해주었기 때문에 타입을 맞춰주기 위해 사용하였다.
다른 방법으로는
const { postId } = +req.params;
const post = await prisma.posts.findFirst({
where: {
postId: postId,
},
...
다음과 같이 req.params 앞에 + 기호를 붙여 Int 타입으로 변환시키는 방법 또한 가능하다.
Insomnia 테스트 클라이언트의 URL 입력 부분을 보면 3 이라고 postId 를 적어서 보내주었다.
실행 결과 postId 가 3에 해당하는 게시글의 모든 정보(패스워드는 제외하는 것이 논리상 옳다) 를 반환해주었다.
게시글을 수정한다면, 단순하게 생각해서 필요한 조건들이 여러개 있을 것이다.
먼저 어떤 게시글을 수정할 것인지 postId 가 필요하고, 인증을 위해 게시글을 작성한 사람과 수정하려는 사람의 비밀번호가 일치해야 할 것이다. 또한 바꾸려는 내용에 대한 데이터도 필요할 것이다.
로직을 간단히 말로 작성해보면
1. params 로 어떤 게시글을 수정할 지 postId 전달
2. 변경할 title, content 와 권한 인증을 위한 password 를 body로 전달
3. postId 를 기준으로 검색해 해당 데이터가 존재하는지 조회
4. 게시글이 조회된다면 password 가 일치하는지 조회
5. 모두 완료되었다면 전달받은 내용으로 게시글 수정
으로 5단계가 될 것이다.
이를 코드로 작성해보자.
router.put('/posts/:postId', async (req, res, next) => {
const { postId } = req.params;
const { title, content, password } = req.body;
const post = await prisma.posts.findUnique({
where: { postId: +postId }
});
if (!post) return res.status(404).json({ message: "게시글이 존재하지 않습니다." });
if (post.password !== password)
return res.status(401).json({ message: "비밀번호가 일치하지 않습니다." });
await prisma.posts.update({
data: {
title: title,
content: content,
},
where: {
postId: +postId,
password: password
}
});
return res.status(200).json({ data: "게시글이 수정되었습니다." });
});
params 로 postId, body 로 title, content, password 를 전달받았다.
두개의 if문은 3, 4번의 과정으로 전달받은 데이터가 유효한 데이터인지 확인하는 과정이다.
이후 update 메서드를 통해 전달받은 데이터로 posts 테이블의 데이터를 변경하고 있다.
분기를 나눴다면 당연히 테스트를 해봐야 완벽한 코드가 될 것이다.
postId = 3인 경우는 존재하지만 비밀번호가 틀린 경우도 완벽하게 잡아낼 수 있다.
올바른 비밀번호로 다시 수정해서 테스트하면
게시글이 수정되었다는 응답을 받을 수 있고
이를 게시글 상세 조회 API로 검색해보면
수정한 데이터로 변경된 것을 확인할 수 있다.
또한 createdAt 과 updatedAt 의 정보가 달라, 이 게시글은 생성 이후에 한번 수정되었다는 것을 알 수 있다.
전체적인 로직 자체는 수정 API 와 동일한데, 차이점으로는 body에서 인증을 위한 password 만 입력받으면 된다는 것이다.
당연하게도 title 이나 content 는 어차피 지울 데이터이기 때문에 입력받을 필요가 없다.
// 게시글 삭제 API
router.delete('/posts/:postId', async (req, res, next) => {
const { postId } = req.params;
// PW 만 body 에서 입력받으면 됨
const { password } = req.body;
const post = await prisma.posts.findUnique({
where: { postId: +postId }
});
if (!post) return res.status(404).json({ message: "게시글이 존재하지 않습니다." });
if (post.password !== password)
return res.status(401).json({ message: "비밀번호가 일치하지 않습니다." });
// 여기가 수정 API와 다른 로직 부분
await prisma.posts.delete({ where: { postId: +postId } });
return res.status(200).json({ data: "게시글이 삭제되었습니다." });
});
실행해보면
다음과 같이 게시글이 삭제되었다는 메세지가 정상적으로 출력된다.
이후 게시글 전체 목록 조회를 해보면
postId = 3의 데이터가 사라진 것을 확인할 수 있다.
#8. JWT(Json Web Token) (1) | 2024.12.19 |
---|---|
#6. ORM - Prisma (0) | 2024.12.17 |
#5. Raw Query (1) | 2024.12.13 |
#4. SQL 제약 조건 (0) | 2024.12.12 |
#3. SQL 기초 (0) | 2024.12.11 |