2024.05.02 프로그래머스-express 라우팅 분리해보기
새로운 프로젝트 만들기
- 팬카페 컨셉의 사이트
- 회원은 여러명의 아티스트의 팬이 될 수 있다.
- https://github.com/ykdman/fantom
기능
회원
- 로그인
- 회원 정보 조회
- 회원 가입
- 회원 탈퇴
팬 되기
- 회원이 팬을 맺은 아티스트를 위한 팬 개인의 기록소 만들기
- 기록소 수정
- 기록소 삭제
API 설계
회원
로그인 POST
‘/login’ - req : body (id, pw)
- res : ‘로그인 성공’
- redirect : main page
회원 가입 POST
‘/signup’ - req : body (id, pwd, name)
- res : 회원가입을 환영합니다.
- redirect : ?
회원 정보 조회 GET
‘/users/:id’ - req : params (id)
- res : json (id, pwd?, name)
- redirect :
회원 탈퇴 DELETE
‘/users/:id’ - req : params (id)
- res : message 탈퇴가 완료되었습니다.
##
Architecture
- 소스 구성
- src
- model : 데이터 저장소
- controllers : 핸들러 함수 저장소
- index.js : 서버 코드
회원가입 api 작성
- method :
POST
- user.controller.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/** user.controller.js*/
const db = require("../model/data.js");
/**
* user 회원 가입 API : POST
* @param {Request} req
* @param {Response} res
* @returns {Response}
*/
const signupUser = (req, res) => {
const { id, pwd, name } = req.body;
const validReq = validRequest([id, pwd, name]);
if (!validReq) {
res.status(400).send("올바른 가입정보를 입력하세요");
return;
}
const existId = db.get(id);
if (existId) {
res.status(400).send(`이미 존재하는 아이디 입니다. : ${id}`);
return;
} else {
const newUser = {
pwd,
name,
};
db.set(id, newUser);
res.status(200).json({ id, ...newUser, message: "회원가입 완료" });
}
};
module.exports = {
loginUser,
signupUser,
};
- index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const express = require("express");
const app = express();
const PORT = 3000;
// controllers
const userControllers = require("./controllers/user.contorller.js");
app.listen(PORT, () => {
console.log(`server listen : ${PORT}`);
});
// body Parser
app.use(express.json());
// middlw Ware (logger)
app.use((req, res, next) => {
const start = Date.now();
console.log(`start: ${req.method} ${req.url}`);
next(); // router 실행
const diffTime = Date.now() - start;
console.log(`end: ${req.method} ${req.baseUrl}${req.url} ${diffTime}ms`);
});
app.post("/signup", userControllers.signupUser);
- 추가적으로 body 유효성 검증을 위한 util 함수를 하나 user.controller.js에 작성하였다.
1
2
3
4
5
6
7
8
9
function validRequest() {
for (const item of [...arguments]) {
if (typeof item === "undefined") {
return false;
}
}
return true;
}
로그인 API 작성
- method :
POST
- user.controller.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
/** user.controller.js*/
/**
* user 로그인 API : POST
* @param {Request} req
* @param {Response} res
* @returns {Response}
*/
const loginUser = (req, res) => {
const { id, pwd } = req.body;
console.log(typeof pwd);
const validReq = validRequest([id, pwd]);
const idExist = db.get(id);
// 계정정보를 올바르게 입력 안했을때
if (!validReq) {
res.status(400).send("올바른 로그인 정보를 입력해주세요");
return;
}
// db가 비어있거나, 일치하는 id 가 없을 때
if (db.size === 0 || !idExist) {
res.status(404).send("해당 ID가 존재하지 않습니다.");
return;
}
// ID 존재 상황
if (idExist) {
const dbUser = db.get(id);
console.log(dbUser);
if (dbUser && dbUser.pwd === pwd) {
res.status(200).send(`${id} : ${dbUser.name} 님 로그인을 환영합니다.`);
} else {
res.status(400).send("비밀번호가 일치하지 않습니다.");
}
}
};
- index.js 작성
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
const express = require("express");
const app = express();
const PORT = 3000;
// controllers
const userControllers = require("./controllers/user.contorller.js");
app.listen(PORT, () => {
console.log(`server listen : ${PORT}`);
});
// body Parser
app.use(express.json());
// middlw Ware (logger)
app.use((req, res, next) => {
const start = Date.now();
console.log(`start: ${req.method} ${req.url}`);
next(); // router 실행
const diffTime = Date.now() - start;
console.log(`end: ${req.method} ${req.baseUrl}${req.url} ${diffTime}ms`);
});
app.post("/signup", userControllers.signupUser);
// login
app.post("/login", userControllers.loginUser);
회원 개별 조회 API 작성
- method :
GET
- user.controller.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/**
* 회원 개별 조회 API : GET
* @param {Request} req
* @param {Response} res
*/
const getUser = (req, res) => {
const { id } = req.params;
const dbUser = db.get(id);
//db에 회원 id가 없는 경우
if (!dbUser) {
res.status(404).send(`${id} 회원이 존재하지 않습니다.`);
}
const user = {
id,
pwd: dbUser.pwd,
name: dbUser.name,
};
res.status(200).json(user);
};
module.exports = {
loginUser,
signupUser,
getUser,
};
- index.js
1
app.get("/users/:id", userControllers.getUser);
개별 회원 삭제 API
- method :
POST
- user.controller.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* 개별 회원 탈퇴 API : DELETE
* @param {Request} req
* @param {Response} res
*/
const deleteUser = (req, res) => {
const { id } = req.params;
const validReq = validRequest([id]);
if (!validReq) {
res.status(400).send("올바른 id를 입력해주세요");
}
const dbUser = db.get(id);
// 올바른 user 존재 시 삭제
if (dbUser) {
const userDeleteState = db.delete(id);
if (userDeleteState) {
res.status(200).send(`${id} 회원이 정상적으로 탈퇴 되었습니다.`);
} else {
res.status(505).send(`${id}의 탈퇴가 이루어지지 않았습니다.`);
}
}
};
- index.js 라우터 코드 추가
1
app.delete("/users/:id", userControllers.deleteUser);
회원이 좋아하는 artist API 설계
- 좋아하는 Artist 페이지 생성
- Artist 페이지 수정
- Artist 페이지 삭제
pages 의 데이터를 저장하기 위한 js파일
- model \ pagesData.js
1
2
3
4
5
const pageData = new Map();
pageData.set("admin", { pageName: "admin Page", artistName: "admin" });
module.exports = pageData;
pages.controller.js 코드 작성 (API)
좋아하는 artist 페이지 생성
팬 페이지 생성 : POST
‘/pages’ - req : body (artistName, pageName)
res : artist의 팬이 되어 페이지를 생성했습니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
// controllers \ pages.controller.js /** * 페이지 생성 API : POST | '/pages/' * @param {import("express").Request} req * @param {import("express").Response} res */ const createPage = (req, res) => { const { artistName, pageName, id } = req.body; const pageExist = pageData.has(id); if (!pageExist) { pageData.set(id, { artistName, pageName }); res .status(200) .json({ id, artistName, pageName, message: `페이지 생성 완료 : ${id}` }); } else { res.status(400).json({ message: "이미 존재하는 페이지 입니다." }); } };
artist 팬 페이지 수정
팬 페이지 수정 : PUT
‘/pages/:id’ - req : url (name)
res : 페이지명이 성공적으로 수정 되었습니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
// controllers \ pages.controller.js /** * 페이지 정보 수정 API : PUT | '/pages/:id' * @param {import("express").Request}} req * @param {import("express").Response} res * @returns */ const updatePage = (req, res) => { const { id } = req.params; const { artistName, pageName } = req.body; const existPage = pageData.get(id); if (!artistName || !pageName) { res.status(400).send("올바른 정보를 입력해주세요."); return; } if (!existPage) { res.status(404).send("수정하려는 페이지를 찾을 수 없습니다."); return; } pageData.set(id, { artistName, pageName }); res.status(200).json({ message: "페이지 정보가 수정되었습니다.", id, artistName, pageName, }); };
artist 팬 페이지 삭제
팬 페이지 삭제 : DELETE
‘/pages/:id’ - req : url (name)
res : 페이지가 성공적으로 삭제 되었습니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
// controllers \ pages.controller.js /** * 페이지 삭제 API : DELETE | '/pages/:id' * @param {import("express").Request} req * @param {import("express").Response} res */ const deletePage = (req, res) => { const { id } = req.params; const existPage = pageData.get(id); if (!existPage) { res.status(404).json({ message: "삭제할 페이지를 찾을 수 없습니다." }); return; } pageData.delete(id); res .status(200) .json({ message: "페이지를 삭제하였습니다.", pageId: existPage.id }); };
내 페이지 전체 조회
페이지 전체 조회 : GET
‘/pages’ - req : X
res : 페이지 전체 데이터
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
// controllers \ pages.controller.js /** * pages 전체 조회 API * @param {import("express").Request} req * @param {import("express").Response} res */ const getPages = (req, res) => { const pages = []; [...pageData.keys()].forEach((key) => { const page = { id: key, pageName: pageData.get(key).pageName, artistName: pageData.get(key).artistName, }; pages.push(page); }); res.status(200).json(pages); };
페이지 개별 조회
개별 페이지 조회 : GET
‘/pages/:id’ - req : url (name)
res : 페이지 개별 데이터
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
// controllers \ pages.controller.js /** * 한 페이지 조회 API : GET | '/pages/:id' * @param {import("express").Request} req * @param {import("express").Response} res */ const getOnePage = (req, res) => { console.log(req.params); const { id } = req.params; const page = pageData.get(id); if (!page) { res.status(404).json({ message: "페이지를 찾을 수 없습니다." }); } else { const pageInfo = { id, ...page }; res.status(200).json(pageInfo); } };
서버 라우팅을 위한 코드
index.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
const express = require("express"); const app = express(); const PORT = 3000; // controllers const userControllers = require("./controllers/user.contorller.js"); // Router const pagesRouter = require("./routes/page.router.js"); app.listen(PORT, () => { console.log(`server listen : ${PORT}`); }); // body Parser app.use(express.json()); // middlw Ware (logger) app.use((req, res, next) => { const start = Date.now(); console.log(`start: ${req.method} ${req.url}`); next(); // router 실행 const diffTime = Date.now() - start; console.log(`end: ${req.method} ${req.baseUrl}${req.url} ${diffTime}ms`); }); // pages 라우터 app.use("/pages", pagesRouter);
routes\page.router.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
// routes/page.router.js const express = require("express"); const pagesRouter = express.Router(); const pagesController = require("../controllers/pages.controller.js"); /** * /pages 라우터 * - get : pages 전체 정보 조회 * - post : pages 생성 */ pagesRouter .route("/") .get(pagesController.getPages) .post(pagesController.createPage); // /pages/:id 라우터 // get // post pagesRouter .route("/:id") .get(pagesController.getOnePage) .put(pagesController.updatePage) .delete(pagesController.deletePage); module.exports = pagesRouter;
코드를 작성하면서 느낀점
- Routing을 지금 내수준에서 분리할 만큼 하고나니까, 처음에는 적응이 어려웠는데, 계속 보다보니 확실히 수정할 부분을 쉽게 찾을 수 있었다.
분리원칙
을 조금 찾아보고 적용했다!
- 데이터베이스 역할을 하는 Map 객체를 사용 했는데, Map에 대한 method 들을 잘 몰랐지만 MDN 으로 검색해서 이것저것 알게 되었다.
- delete, set, get
- set 은 심지어 재할당 기능까지 있는 것을 보고 놀랐다.
- delete, set, get
- 지금 같이 간단한 백엔드 코드도 300줄 정도가 넘어가는데, 실제 서비스로 운영 되고 있는 백엔드 코드들은 얼마나 장황할지가 궁금하다…
- 백엔드도 상당히 재밌다.
- express는 자유도가 높아도 너무 높은것 같다.
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.