[Node.js] 라우터, 컨트롤러 분리하기
애플리케이션
특정 기능을 제공하는 프로그램. 그 자체로 완전한 소프트웨어
사용자 인터페이스, 사용자가 상호 작용하는 기능을 모두 포함한다.
API
애플리케이션 간에 자료를 주고 받으면서 특정 기능을 실행하는 코드
API를 사용하면 다른 시스템끼리 자료를 주고 받을 수도 있고, 새로운 애플리케이션을 만들 수도 있음
예 : SNS 애플리케이션에서 사용하는 로그인 API, 게시물 작성 API
예 : 소셜 로그인 API 처럼 API를 공개할 경우, 다른 애플리케이션에서 사용 가능
REST
HTTP 프로토콜(GET, POST, PUT, DELETE, PATCH) 활용해서 데이터를 주고받는 방식을 약속해놓은 것
RESTful API (= REST API)
HTTP 프로토콜을 활용해서 자료를 주고 받는 API를 Restful한 API 라 한다 (REST를 잘 지켜서 개발한 API)
RESTful API의 가장 큰 특징은 URI로 자원을 요청한다. (예 : localhost:3000/contacts/10 로 접근하면, contacts라는 경로로 id=10인 데이터를 GET 요청 하겠다는 의미)
* URI란, URL보다 더 큰 개념으로 서버의 파일/정보에 접근할 때, 주소를 사용해서 접근하는 방식을 말한다.
URI를 지정할 때 약속 (RESTful 한 API 규칙 중 하나)
자원 이름은 명사형으로 알파벳 소문자 사용 : contacts
자원 이름으로 단어를 2개 이상 사용시 붙임표(-) 연결 혹은 카멜 표기법 사용 : vscode-docs, bestChallenge
자원 간 계층이 있다면 슬래시(/)로 구분하되, URI 끝에는 붙이지 않음 : channel/90/home
자원 처리 방법은 URI에 포함시키지 않음 : localhost:3000/contacts(o) localhost:3000/get-contacts(x)
RESTful API의 HTTP 요청 방식
HTTP 프로토콜 | 역할 | 설명 |
POST | Create | 생성 |
GET | Read | 조회 |
PUT | Update | 수정 |
DELETE | Delete | 삭제 |
역할의 앞글자만 따서 CRUD 라 한다.
MVC 패턴
애플리케이션 코드를 기능이나 역할에 따라 파일을 나누고,
각각을 어떻게 연결해서 사용할지 서술해놓은 방법론을 "디자인패턴"이라 한다.
어떻게 연결하느냐는 디자인패턴 마다 다르다.
그 중, MVC패턴은 model, view, controller 로 나눈 디자인 패턴이다.
영역 | 설명 |
Model | 데이터베이스에 어떤 자료를 처리하기 위해, 자료의 형식이나 모델 이름을 지정해주는 부분으로, 애플리케이션에서 처리할 대상을 말한다. 데이터베이스를 통해 자료를 저장, 검색, 수정하는 함수들이 모델에 해당한다. |
View | 처리하고 난 결과를 화면에 보여주거나, 사용자가 어떤 값을 입력할 수 있도록 form 을 보여주는, 눈에 보이는 화면 단 |
Controller | 실제로 API가 처리할 기능들이 모여있는 곳 (예: 라우트 코드를 작성한 파일) 모델과 뷰 중간에 위치하면서, 요청에 따라 모델이나 뷰를 수정하는 역할 |
1. 브라우저에서 서버로 어떠한 요청을 보낸다. (모든 연락처 정보를 보여줘)
2. 컨트롤러는 클라이언트의 요청을 받고, 모델에게 요청을 전송한다. (모든 연락처 정보를 조회할 수 있는 쿼리와 매핑된 함수 호출해줘)
3. 모델은 데이터베이스에게 모든 연락처 정보를 조회해달라고 요청
4. 데이터베이스는 얻어진 데이터를 모델에게 넘겨줌
5. 모델은 컨트롤러에게 넘겨줌
6. 컨트롤러는 뷰에게 넘겨줌
7. 뷰는 이 정보를 클라이언트(브라우저)에 넘겨줌
뷰와 컨트롤러 사이에 라우터가 존재한다면, 위와 같은 구조로 MVC 패턴이 결정된다.
1. 브라우저에서 모든 연락처 정보를 보여달라고 요청
2. 요청 정보는 라우터를 통해 컨트롤러로 연결
3~8의 과정은 일반 MVC 패턴과 동일하게 작동
Controller 와 라우터 분리하기
브라우저에서 localhost:3000/contacts 로 접속하면,
먼저 라우터에서 /contacts 경로의 get 요청을 받고,
그 다음 컨트롤러를 호출하여 데이터 조회를 할 수 있게 바꿀 것이다.
myContacts > app.js
const express = require('express');
const dbConnect = require('./config/dbConnect');
const app = express();
dbConnect(); //DB 접속
//메인 화면
app.get("/", (req, res)=>{
res.send("Hello Node!");
})
//바디파서 미들웨어 등록
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
//라우터 파일
app.use("/contacts", require("./routes/contactRoutes"));
app.listen(3000, () =>{
console.log("서버 실행중");
})
서버를 실행하면 가장먼저 실행되는 파일인 app.js 이다.
/contacts 경로로 접근할 경우,
myContacts > routes > contactRoutes.js 파일에서 라우팅 처리를 하도록 만들어둔 상태이다.
myContacts > routes > contactRoutes.js
const express = require('express');
const router = express.Router();
//목록조회, 데이터 저장
router.route("/")
.get((req,res)=>{
res.send("contacts 목록 조회");
})
.post((req,res)=>{
const { id, name } = req.body; //구조분해할당
if( !id || !name ){
return res.send("필수 값이 입력되지 않았습니다.");
}
res.send("contacts 데이터 create")
});
//(특정 id) 조회, 수정 삭제
router.route("/:id")
.get((req, res)=>{
res.send(`특정 id ${req.params.id}의 연락처 조회`);
})
.put((req, res)=>{
res.send(`특정 id ${req.params.id}의 연락처 수정`);
})
.delete((req, res)=>{
res.send(`특정 id ${req.params.id}의 연락처 삭제`);
});
module.exports = router;
/contacts 로 시작하는 모든 주소는 위 라우터 파일에서 처리해주고 있다.
localhost:3000/contacts 로 접근시 (Get 요청으로 /contacts 접근 시)
.get( 어쩌구저쩌구 ) 에 해당하는
(req, res) =>{ res.send("contacts 목록 조회") } 부분을 컨트롤러로 분리해줄 것이다.
먼저 myContacts 프로젝트 아래 controllers 폴더 생성한다.
myContacts > controllers > contactController.js
// Get All contacts
// Get /contacts
const getAllContacts = async(req, res) => {
try{
res.send("contacts 목록 조회22");
}catch(error){
res.send(error);
}
}
module.exports = getAllContacts;
위처럼 getAllContacts 함수를 작성하였고, 해당 함수를 모듈로 만들어 내보냈다.
이제 이 함수를 라우터 파일에서 가져와 쓰면 된다.
※ 위 코드처럼 try-catch문이 단순히 에러 여부만 체크하는 거라면 async-handler 모듈을 사용하면 코드 가독성이 좋아진다. npm i express-async-handler 로 설치한다.
const asyncHandler = require('express-async-handler');
// Get All contacts
// Get /contacts
const getAllContacts = asyncHandler(async(req, res) => {
res.send("contacts 목록 조회22");
});
module.exports = getAllContacts;
위와 같이 async(req,res)=>{} 의 내용을 asyncHandler()로 묶으면 try-catch문 생략이 가능하다.
에러 여부는 asyncHandler가 처리한다. ㅎㅎ
다시 라우터 파일로 돌아가서 컨트롤러 함수를 호출할 수 있게 코드를 수정한다.
myContacts > routes > contactRoutes.js
const express = require('express');
const router = express.Router();
const getAllContacts = require('../controllers/contactController')
//목록조회, 데이터 저장
router.route("/")
/*
.get((req,res)=>{
res.send("contacts 목록 조회");
})
*/
.get(getAllContacts)
.post((req,res)=>{
const { id, name } = req.body; //구조분해할당
if( !id || !name ){
return res.send("필수 값이 입력되지 않았습니다.");
}
res.send("contacts 데이터 create")
});
// (... 생략 ...)
module.exports = router;
상단에
const getAllContacts = require('../controllers/contactController') 선언하여 ( ./는 routes 폴더, ../는 myContacts 폴더 )
myContacts > controllers > contactController.js 파일을 가져오고,
router.route("/").get() 함수의 인자 부분을 .get(getAllContacts) 로 변경하여 작성한다.
최종 코드
myContacts > controllers > contactController.js
const asyncHandler = require('express-async-handler');
// Get All contacts
// Get /contacts
const getAllContacts = asyncHandler(async(req, res) => {
res.send("contacts 목록 조회");
});
// Create contact
// Post /contacts
const createContact = asyncHandler(async(req, res) => {
console.log(req.body);
const {name, email, phone} = req.body;
if(!name || !email || !phone){
return res.send("필수 값이 입력되지 않았습니다.");
}
res.send("contacts 데이터 create");
});
// Get Contact
// Get /contacts/:id
const getContact = asyncHandler(async(req, res) => {
res.send(`특정 id ${req.params.id}의 연락처 조회`);
});
// update Contact
// Put /contacts/:id
const updateContact = asyncHandler(async(req, res) => {
res.send(`특정 id ${req.params.id}의 연락처 수정`);
});
// delete Contact
// Delete /contacts/:id
const deleteContact = asyncHandler(async(req, res) => {
res.send(`특정 id ${req.params.id}의 연락처 삭제`);
});
module.exports = {
getAllContacts
, createContact
, getContact
, updateContact
, deleteContact
};
/contacts 경로로의 GET, POST 요청
/contacts/:id 경로로의 GET,PUT,DELETE 요청
위 5가지 함수를 컨트롤러에 작성했다.
각각의 함수 안에 CRUD 하는 작업은 다음 포스팅에서!!
myContacts > routes > contactRoutes.js
const express = require('express');
const router = express.Router();
const { getAllContacts
, createContact
, getContact
, updateContact
, deleteContact
} = require('../controllers/contactController')
//목록조회, 데이터 저장
router.route("/")
.get(getAllContacts)
.post(createContact)
;
//(특정 id) 조회, 수정 삭제
router.route("/:id")
.get(getContact)
.put(updateContact)
.delete(deleteContact)
;
module.exports = router;
위처럼 .get() .post() 함수 인자에 컨트롤러 함수를 각각 작성해주었다.
라우트 코드가 훨씬 간단해졌다.
정리
/contacts 경로로 GET/POST 요청이 오면,
라우터 파일에는 각 요청에 맞게 컨트롤러 함수와 연결한다.
그리고 기능 구현은 컨트롤러 함수에 작성한다.
이렇게 라우트 코드와 컨트롤러를 분리해서 작성하게 되면,
라우트 코드 영역에서는 컨트롤러가 무슨 일을 하는지 알 필요가 없게 된다.
기능 수정이 필요할 경우, 라우트 코드는 수정할 필요가 없어지고, 컨트롤러 함수 내용만 수정하면 된다.
https://youtu.be/rKkE0o7JbvY?feature=shared