id, password의 조합으로 이루어지겠죠?1) [게시판 프로젝트] express 시작하기
📌 이번 섹션은 게시판 프로젝트의 로그인, 회원가입기능을 구현할 예정입니다.
📚 [게시판 프로젝트] API 명세서
[ Prisma 게시판 프로젝트 API 명세서](https://www.notion.so/d9df8786b67740598af3d47aea4c38fb?pvs=21)
Express.js를 이용한 게시판 프로젝트를 시작하기 전에, 먼저 app.js에서 기본적인 토대를 만들고 간단한 API를 구현해보겠습니다. 또한, 모든 Router에서 Prisma를 사용할 수 있도록 utils/prisma/index.js 파일을 생성하겠습니다. 😎
[코드스니펫][게시판 프로젝트] app.js 초기화
// src/app.js
import express from 'express';
import cookieParser from 'cookie-parser';
const app = express();
const PORT = 3018;
app.use(express.json());
app.use(cookieParser());
app.use('/api', []);
app.listen(PORT, () => {
console.log(PORT, '포트로 서버가 열렸어요!');
});
[코드스니펫][게시판 프로젝트] Prisma 초기화
// src/utils/prisma/index.js
import { PrismaClient } from '@prisma/client';
export const prisma = new PrismaClient({
// Prisma를 이용해 데이터베이스를 접근할 때, SQL을 출력해줍니다.
log: ['query', 'info', 'warn', 'error'],
// 에러 메시지를 평문이 아닌, 개발자가 읽기 쉬운 형태로 출력해줍니다.
errorFormat: 'pretty',
}); // PrismaClient 인스턴스를 생성합니다.
📒 [Directory Structure][게시판 프로젝트] 프로젝트 초기화
```jsx
.
├── package.json
├── prisma
│ └── schema.prisma
├── src
│ ├── app.js
│ └── utils
│ └── prisma
│ └── index.js
└── yarn.lock
```
2) [게시판 프로젝트] 회원가입 API
// app.js
import express from 'express';
import cookieParser from 'cookie-parser';
import UsersRouter from './routes/users.router.js';
const app = express();
const PORT = 3018;
app.use(express.json());
app.use(cookieParser());
app.use('/api', [UsersRouter]);
app.listen(PORT, () => {
console.log(PORT, '포트로 서버가 열렸어요!');
});
3) [게시판 프로젝트] bcrypt
👉 일반적으로, 사용자의 비밀번호를 데이터베이스에 저장할 때, 보안을 위해 비밀번호를 평문으로 저장하지 않고 암호화 하여 저장합니다.
→ [개인정보보호법](https://www.law.go.kr/%EB%B2%95%EB%A0%B9/%EA%B0%9C%EC%9D%B8%EC%A0%95%EB%B3%B4%EB%B3%B4%ED%98%B8%EB%B2%95)에서는 주민등록번호와 같은 정보는 암호화되어 저장되어야합니다.
4) [게시판 프로젝트] 회원가입 API Bcrypt 리팩토링
// src/routes/users.router.js
import bcrypt from 'bcrypt';
/ 사용자 회원가입 API 리팩토링/
router.post('/sign-up', async (req, res, next) => {
const { email, password, name, age, gender, profileImage } = req.body;
const isExistUser = await prisma.users.findFirst({
where: {
email,
},
});
if (isExistUser) {
return res.status(409).json({ message: '이미 존재하는 이메일입니다.' });
}
// 사용자 비밀번호를 암호화합니다.
const hashedPassword = await bcrypt.hash(password, 10);
// Users 테이블에 사용자를 추가합니다.
const user = await prisma.users.create({
data: {
email,
password: hashedPassword, // 암호화된 비밀번호를 저장합니다.
},
});
// UserInfos 테이블에 사용자 정보를 추가합니다.
const userInfo = await prisma.userInfos.create({
data: {
UserId: user.userId, // 생성한 유저의 userId를 바탕으로 사용자 정보를 생성합니다.
name,
age,
gender: gender.toUpperCase(), // 성별을 대문자로 변환합니다.
profileImage,
},
});
return res.status(201).json({ message: '회원가입이 완료되었습니다.' });
});
5) [게시판 프로젝트] 로그인 API, 사용자 인증 미들웨어
// src/routes/users.route.js
import jwt from 'jsonwebtoken';
/ 로그인 API /
router.post('/sign-in', async (req, res, next) => {
const { email, password } = req.body;
const user = await prisma.users.findFirst({ where: { email } });
if (!user)
return res.status(401).json({ message: '존재하지 않는 이메일입니다.' });
// 입력받은 사용자의 비밀번호와 데이터베이스에 저장된 비밀번호를 비교합니다.
else if (!(await bcrypt.compare(password, user.password)))
return res.status(401).json({ message: '비밀번호가 일치하지 않습니다.' });
// 로그인에 성공하면, 사용자의 userId를 바탕으로 토큰을 생성합니다.
const token = jwt.sign(
{
userId: user.userId,
},
'customized_secret_key',
);
// authotization 쿠키에 Berer 토큰 형식으로 JWT를 저장합니다.
res.cookie('authorization', Bearer ${token});
return res.status(200).json({ message: '로그인 성공' });
});
6) [게시판 프로젝트] 사용자 인증 미들웨어
// src/middlewares/auth.middleware.js
import jwt from 'jsonwebtoken';
import { prisma } from '../utils/prisma/index.js';
export default async function (req, res, next) {
try {
const { authorization } = req.cookies;
if (!authorization) throw new Error('토큰이 존재하지 않습니다.');
const [tokenType, token] = authorization.split(' ');
if (tokenType !== 'Bearer')
throw new Error('토큰 타입이 일치하지 않습니다.');
const decodedToken = jwt.verify(token, 'customized_secret_key');
const userId = decodedToken.userId;
const user = await prisma.users.findFirst({
where: { userId: +userId },
});
if (!user) {
res.clearCookie('authorization');
throw new Error('토큰 사용자가 존재하지 않습니다.');
}
// req.user에 사용자 정보를 저장합니다.
req.user = user;
next();
} catch (error) {
res.clearCookie('authorization');
// 토큰이 만료되었거나, 조작되었을 때, 에러 메시지를 다르게 출력합니다.
switch (error.name) {
case 'TokenExpiredError':
return res.status(401).json({ message: '토큰이 만료되었습니다.' });
case 'JsonWebTokenError':
return res.status(401).json({ message: '토큰이 조작되었습니다.' });
default:
return res
.status(401)
.json({ message: error.message ?? '비정상적인 요청입니다.' });
}
}
}
7) [게시판 프로젝트] 사용자 정보 조회 API
// src/routes/users.route.js
/ 사용자 조회 API /
router.get('/users', authMiddleware, async (req, res, next) => {
const { userId } = req.user;
const user = await prisma.users.findFirst({
where: { userId: +userId },
select: {
userId: true,
email: true,
createdAt: true,
updatedAt: true,
UserInfos: {
// 1:1 관계를 맺고있는 UserInfos 테이블을 조회합니다.
select: {
name: true,
age: true,
gender: true,
profileImage: true,
},
},
},
});
return res.status(200).json({ data: user });
});