프로젝트에서 가장 처음에 구현하는 기능 중 하나는 로그인과 회원가입입니다. 저도 마찬가지로 이 기능을 구현함으로서 전체 프로젝트의 API와 데이터 전송 스타일 등 API 호출할때의 틀을 먼저 잡는 것 같습니다. 그래서 오늘은 간단하게 구현했던 로그인, 회원가입 API를 기록해보도록 하겠습니다.
저는 Node.js로 사용하는게 처음이라서 기본적인 API를 구현하려고 합니다. 그래서 MVC 패턴을 사용해서 기능을 구현하려고 합니다. 이전에 포스팅했던 node.js 폴더구조과 동일하게 구현해보겠습니다.
https://gamza1013.tistory.com/130
[Node] Node.js 백엔드 폴더 구조
진행 중인 프로젝트가 Node.js로 백엔드 API를 만들고 있습니다. 현재 프로젝트에서는 기본적인 RestAPI와 소켓통신도 해야하고 약 3가지 정도의 Open API를 사용할 예정입니다. 백엔드 개발은 자바로
gamza1013.tistory.com
1. App.js(index.js) -> route 파일
// index.js
const express = require('express');
const errorHandler = require('./middlewares/errorHandler')
// db 연동
const mysql = require('./config/db');
mysql.pool;
// 라우터
const userRoutes = require("./routes/userRoutes")
const app = express();
const PORT = 4000;
// JSON 형식의 요청 본문을 처리할 수 있도록 설정
app.use(express.json());
// 컨트롤러, 서비스 매핑 라우터
app.use('/api/users', userRoutes); // 회원 정보 라우터
// 에러 처리 미들웨어
app.use(errorHandler);
// 서버 시작
app.listen(PORT, () => {
console.log(`Server is running on http://localhost:${PORT}`);
});
위 코드처럼 에러 처리 미들웨어를 제외하고는 모두 필요한 코드입니다.
app.use('api/users', userRoutes);
가 router를 처리하는 코드입니다. 위 코드를 제 방식대로 해석하면 " app은 '/api/users'라는 게 포함되어 있는 router라면 userRoutes(./routes/userRoutes)파일 내용을 확인할 것이다 "라고 해석했습니다!
그렇다면 만약 api 호출 방식이 api/notice라면 공지사항에 대한 내용이겠고, api/board라면 게시판과 같이 route해주면 될 것입니다.
2. Route.js 파일 -> controller
// 라우터 : URL 경로와 컨트롤러를 매핑하는 라우트 파일을 정의합니다.
const express = require('express');
const {
c_getUsers,
c_createUser,
c_signinUser
} = require('../controllers/userController.js');
const router = express.Router();
// 메소드별 정리
// get
router.get('/', c_getUsers);
// router.get('/:id', c_getUserInfo);
// post
router.post('/signup', c_createUser);
router.post('/signin', c_signinUser);
// put
// router.put("/:id", putUserInfo);
// delete
// router.delete("/:id", deleteUser);
module.exports = router;
이제 우리가 알고 있는 get, post, put, delete방식을 사용하게 됩니다.
router.get('/', c_getUsers); 코드는 앞(App.js)에서 설정했던 'api/users'와 '/'가 합쳐져서 프론트에서 보낸 get방식의 'api/users/'의 내용이 되는 것입니다.
만약 get방식에서 id값을 보내서 해당 데이터의 상세 정보를 가져오겠다 하면 '/:id'를 사용하시면 됩니다.
원래 자바라면 controller와 서비스의 함수명을 동일하게 썼던것 같은데 이번에는 확실하게 구분할 수 있도록 c_변수명을 사용하기로 했습니다. c_변수명이면 controller의 함수, s_변수명이면 서비스에 있는 함수로 지정했습니다.(사실 변수명을 어떻게 해야하나 생각이 안나서,,ㅎ)
이게 controller로 연결했으니까 controller에서 서비스를 확인해야겠죠?
3. Controller -> Service
const {
getUserList, getUserInfo,
s_createUser, s_signinUser
} = require('../services/userService');
// 상태 코드 (가독성을 위해)
// service에 넣지 않는 이유
const STATUS_CODES = require('../middlewares/statusCode');
// 회원 생성(회원가입)
const c_createUser = async (req, res) => {
try {
const newUser = await s_createUser(req.body);
console.log('control newUser -> ', newUser);
if (!newUser) { // 401: 중복 데이터
return res.status(STATUS_CODES.CONFLICT).json({ message: '중복된 데이터가 있습니다.' });
}
// 회원 가입 성공 시
// json 안이 front로 보내지는 데이터
res.status(STATUS_CODES.CREATED).json({
statusCode: 200,
errorCode: 0,
message: 'Sign Up Successful.',
result: { email: req.body.email, name: req.body.name },
timestamp: new Date(),
});
} catch (error) {
console.log(error);
res.status(STATUS_CODES.INTERNAL_SERVER_ERROR).json({message:"INTERNAL_SERVER_ERROR", error})
}
};
// 로그인
const c_signinUser = async(req, res) => {
try{
console.log('c_signinUser')
// email, password, type이 없을 경우 예외처리
if(!req.body.email || !req.body.password || !req.body.user_type){
res.status(STATUS_CODES.UNAUTHORIZED).json({message: 'Invalid username, password or type.'})
}
// DB 확인 + 해싱 복호화
const result = await s_signinUser(req.body);
if (!result) { // 401: 인증 실패
return res.status(STATUS_CODES.UNAUTHORIZED).json({ message: 'Invalid username, password or type.' });
}
// 로그인 성공
// json 안이 front로 보내지는 데이터
res.status(STATUS_CODES.OK).json({
statusCode: 200,
errorCode: 0,
message: 'Login Up Successful.',
result: { email: result.email, name: result.name, user_type: result.user_type },
timestamp: new Date(),
});
}catch(error){
console.log(error);
res.status(STATUS_CODES.INTERNAL_SERVER_ERROR).json({message:"INTERNAL_SERVER_ERROR", error})
}
}
module.exports = { c_getUsers, c_createUser, c_signinUser };
controller코드입니다!
회원가입과 로그인 코드가 같이 있어서 헷갈려 보이는데 함수별로 확인해주시면 될 것같습니다.
저는 상태코드도 함께 지정해뒀어요.
가장 중요하게 생각했던 것은 response데이터 형식을 가장 중요하게 생각했는데 프론트에서 response type을 지정을 해놨습니다.
그래서 response의 데이터 타입은
res.status(STATUS_CODES.OK).json({
statusCode: 200, // 상태코드
errorCode: 0, // 성공했다면 0
message: 메세지,
result: 전송할 데이터,
timestamp: new Date(), // response 시간
});
로 지정했습니다. 에러가 발생되면 에러 코드와 메세지를 보내어 전송하도록 했습니다. 프론트에서도 회원가입 시 중복데이터가 있을 때 상태코드는 409이므로 예외처리로 409 에러가발생하면 alert창으로 '중복된 정보가 있습니다. 다시 시도해주세요'라고 지정했습니다.
그리고 꿀팁! status코드는 가독성을 위해서 service단에서 작성하지 않고 controller에서 작성한다는 것을 알게 되었습니다!
statusCode 작성한 파일입니다.
const STATUS_CODES = {
OK: 200, // 서버가 요청을 성공적으로 처리하였다.
CREATED: 201, // 요청이 처리되어서 새로운 리소스가 생성되었다.
BAD_REQUEST: 400, // 요청의 구문이 잘못되었다.
UNAUTHORIZED: 401, // 지정한 리소스에 대한 액세스 권한이 없다.
FORBIDDEN: 403, // 지정한 리소스에 대한 액세스가 금지되었다.
NOT_FOUND: 404, // 지정한 리소스를 찾을 수 없다.
CONFLICT: 409, //서버의 현재 상태와 요청이 충돌했음을 나타냅니다(중복)
INTERNAL_SERVER_ERROR: 500, //서버에 에러가 발생하였다.
};
module.exports = STATUS_CODES;
4. service -> repository
const {
getAllUsers,
insertUser,
verifyUser
} = require("../repositories/userRepository")
const {hashPassword, comparePassword} = require("../config/bcrypt")
// 회원 정보 (전체)
const getUserList = async () => {
console.log('service 진입')
const result = await getAllUsers();
console.log('service', result);
return result;
}
// 회원 생성(회원가입)
const s_createUser = async (userData) => {
// 같은 이메일 주소, type이 있는지 확인
const result = await verifyUser(userData);
if (!result){
// DB 추가
const newUser = await insertUser(userData);
console.log('DB 추가 -> ', newUser)
return newUser;
}else {
return null;
}
};
//로그인
const s_signinUser = async(userData) => {
// DB 데이터
const user = await verifyUser(userData);
console.log('user->', user)
if(iscompare){
return user
}else {
return null;
}
}
module.exports = {
getUserList,
s_createUser,
s_signinUser,
};
이제 서비스에서는 repository 데이터를 보냅니다. 그리고 쿼리문을 실행합니다.
Service에서 어떤 쿼리문을 수행해서 처리하도록 로직을 작성하는 코드입니다. repository는 그 쿼리문을 수행하는 파일입니다.
예를 들어 회원가입할 때 같은 회원이 두번 가입하지 않도록 중복 데이터 여부를 확인하고, true이면 insert, false라면 error처리 하는 방식으로 구현하면 될 것같습니다. 원래라면 repository를 따로 안 만들어도 되는데 그래야지 객체화 되어 있는것 같아서 더 꼼꼼하게 처리하려고 합니다.
다음은 repository 입니다.
5. Repository
const {db} = require('../config/db');
const getAllUsers = async() => {
console.log('repository 진입')
const [rows] = await db.query('SELECT * FROM LC_USER_T');
console.log('repository', rows)
return rows;
}
const insertUser = async(userData) => {
try{
const {email, name, password, user_type} = userData;
console.log('repository 진입', userData)
const result = await db.execute(
"INSERT INTO LC_USER_T(email, name, password, user_type ) VALUES (?, ?, ?, ? )",
[email, name, password, user_type])
return result;
}catch(error){
return error;
}
}
const verifyUser = async(userData) =>{
const {email, password, user_type} = userData;
const [result] = await db.query(
'SELECT * FROM LC_USER_T WHERE email = ? AND user_type = ?',
[email, user_type]
)
return result[0];
}
module.exports = {
getAllUsers,
insertUser,
verifyUser
}
이런식으로 select, insert, delete, update 방식을 사용하는 쿼리문을 직접 넣으면 됩니다. 쿼리문에 대한 포스팅 자료 입니다. 한번 확인해보세요!!
https://gamza1013.tistory.com/131
[Node.js] Node와 Mysql 연동하기 node.js, 데이터베이스 연동(Mysql)
프로젝트에서 사용하는 DB는 Mysql을 사용하게 되었습니다. 흔히 프로젝트에서 DB와 연동하는 일은 한번쯤은 꼭 있습니다. 그래서 오늘은 기록용 겸 포스팅 겸 해서 올려보겠습니다. 저는 .env
gamza1013.tistory.com
이런식으로 repository를 마지막으로 수행하고 return, return을 반복해서 repository -> service ->controller -> router 해서 프론트로 값이 response될 것입니다.
mvc 패턴을 알고 있는 분이라면 쉽게 이해하셨을 거라고 생각합니다.
제가 틀린 내용이 있다면 댓글로 꼭 알려주세요!!!
'백 > Node.js' 카테고리의 다른 글
[Node.js] Node와 Mysql 연동하기 쿼리문 사용법, node.js, 데이터베이스 연동(Mysql) (1) | 2024.11.26 |
---|---|
[Node] Node.js 백엔드 폴더 구조 (1) | 2024.11.25 |
[Node.js] Node로 간단한 API 만들기, 웹 서버 만들어보기 (5) | 2024.11.11 |
[Node] Node.js 란? (6) | 2024.10.30 |