게시글

5만명이 선택한 평균 별점 4.9의 제로초 프로그래밍 강좌! 로드맵만 따라오면 됩니다! 클릭
강좌5 - NodeJS - 8년 전 등록 / 3년 전 수정

익스프레스 미들웨어, 라우팅

에러 처리

안녕하세요 이번 시간에는 익스프레스 프레임워크의 미들웨어라우팅에 대해 알아보겠습니다!

미들웨어(Middleware)

익스프레스 프레임워크의 장점 중 하나가 미들웨어를 사용한다는 겁니다. Middleware가 뭘까요? 이름처럼 요청에 대한 응답 과정 중간에 껴서 어떠한 동작을 해주는 프로그램입니다. 익스프레스는 요청이 들어올 때 그에 따른 응답을 보내주는데요. 응답을 보내기 전에 미들웨어가 지정한 동작을 수행합니다. 전 시간에 보았던 express.static이 미들웨어의 한 종류로, 정적 파일의 기본 경로를 정해주는 역할을 합니다.

미들웨어에는 수 많은 것들이 있는데 대표적으로 Morgan, Compression, Session, Body-parser, Cookie-parser, Method-override, Cors, Multer 등이 있습니다. 모두 npm에서 다운받을 수 있습니다. 

간단히 소개하자면, Morgan은 익스프레스 프레임워크가 동작하면서 나오는 메시지들을 콘솔에 표시해줍니다. Compression은 이름처럼 페이지를 압축해서 전송해주고요. Session은 세션을, Body-parser은 폼에서 전송되는 POST 값을, Cookie-parser는 쿠키를 사용할 수 있게 해줍니다. Method-override는 form에서 PUT과 DELETE 메소드를 사용할 수 있게 합니다. Cors는 크로스오리진(다른 도메인 간의 AJAX 요청)을 가능하게 하고요. Multer는 파일업로드를 할 때 주로 쓰입니다.

전 시간에 봤던 express.static도 미들웨어의 한 종류입니다. 예전에는 상술했던 대부분의 미들웨어가 express에 포함되어 있었지만 이제는 express.static을 제외하고 다 분리되어 따로 npm install로 설치해야합니다.

const compression = require('compression');
const cors = require('cors');
const express = require('express');
const app = express();
app.use(compression());
app.use(cors());
app.get(...); // 이전과 동일
app.listen(8080, ...); // 이전과 동일

express에서는 app.use(미들웨어)로 사용할 미들웨어를 응답을 보내기 전에 넣어주면 됩니다. 만약 필요한 게 있다면 npm 검색을 통해서 찾아보세요! 미들웨어는 직접 만들 수도 있습니다. 라우팅 부분(app.get, app.post,...) 위에만 넣으면 됩니다. 간단히 안녕!이라고 말하는 허접한 미들웨어를 만들어보겠습니다. 

app.use((req, res, next) => {
  console.log('안녕!');
  next();
});

네... 끝입니다. 이제부터 request가 올 때마다 콘솔에 안녕하고 외치게 됩니다. 

undefined

일단 const app = express(); 한 후에 app.use로 순차적으로 미들웨어가 시작되는데요. 제가 만든 것에서는 안 쓰였지만, 미들웨어는 request와 response를 매개변수로 받아 조작할 수 있습니다. 위에서 언급한 모든 미들웨어가 이런 식으로 request와 response를 조작한 겁니다. 그리고 마지막으로 next()를 하면 다음 미들웨어로 넘어갑니다. next()를 하지 않으면 더 이상 진행이 되지 않기 때문에 꼭 넣어주셔야합니다.

참고로 에러가 발생했을 때는 next(에러)처럼 next의 인자로 error 정보를 넣어 라우팅 부분으로 넘겨줍니다. 안에
에러 객체를 넣어주었을 때는 상황이 조금 다릅니다. 그 에러는 라우팅에서 처리합니다. 이 글 제일 아래 나와있습니다.

라우팅

익스프레스의 또다른 장점은 라우팅이 편리합니다. 라우팅이라 함은 클라이언트에서 보내는 주소에 따라 다른 처리를 하는 것을 의미하는데요. 익스프레스는 REST API에 따라 처리하는 데 그 방법이 아주 간단합니다.

app 객체에 app[REST메소드]('주소', 콜백함수) 이렇게 연결하는데요. 앞에서 app.get('/', 콜백) 하는 것을 보셨죠? 바로 / 주소로 GET 요청이 올 때 콜백하라고 등록한 겁니다. app.get 외에, app.post, app.put, app.delete 메소드를 사용합니다. (put과 delete를 사용하려면 위에서 알려드린 method-override 패키지를 설치해야합니다)

주소 부분은 정규 표현식도 가능하고 :(콜론)을 사용한 와일드카드도 가능합니다. 예를 들어 app.get('/post/:id')인 경우 /post/a도 적용되고, /post/b도 적용됩니다.

와일드카드를 사용할 때는 순서가 중요합니다.

app.get('/post/:id', () => {});
app.get('/post/a', () => {});

이렇게 되어있다면 /post/a에 요청이 왔을 때 한 눈에 보기엔 사람들은 두 번째 라우터에 걸릴거라고 생각하지만 /post/:id에 먼저 걸립니다. /post/a의 콜백 함수는 실행되지 않습니다. 따라서 와일드카드 라우터는 항상 다른 라우터들보다 뒤에 적어주는 것이 좋습니다.

이렇게 app에 계속 이벤트 리스너처럼 연결하면 되는데, 라우팅은 반복되는 부분이 많기 때문에 주로 모듈로 분리해서 사용합니다. server.js에서 라우팅 부분을 지우고, route.js 파일을 만들어봅시다.

route.js

const express = require('express');
const path = require('path');
const router = express.Router(); // 라우터 분리
router.get('/', (req, res) => { // app 대신 router에 연결
  res.sendFile(path.join(__dirname, 'html', 'main.html'));
});
router.get('/about', (req, res) => {
  res.sendFile(path.join(__dirname, 'html', 'about.html'));
});
module.exports = router; // 모듈로 만드는 부분

express에서는 express.Router()을 사용해 라우터를 분리할 수 있습니다. module.exports가 바로 모듈을 만드는 코드입니다. 이 부분이 있어야 다른 파일에서 여기서 export한 것을 require할 수 있습니다. 모듈 시스템에 대한 자세한 강좌는 여기를 참고하세요. 

이렇게 만든 route.js파일을 server.js에서 불러옵니다. 이전 시간과 변화는 없지만 코드를 분리했다는 것에 의미를 둡시다. 이렇게 코드를 잘게 분리해야 나중에 유지보수가 쉽습니다. 수백 수천 줄이 있는 하나의 긴 파일에서 코드를 찾는 것보다는 기능별로 분리된 파일에서 찾는 게 더 쉽겠죠?

const route = require('./route.js');
...
app.use('/', route);
app.use((req, res, next) => { // 404 처리 부분
  res.status(404).send('일치하는 주소가 없습니다!');
});
app.use((err, req, res, next) => { // 에러 처리 부분
  console.error(err.stack); // 에러 메시지 표시
  res.status(500).send('서버 에러!'); // 500 상태 표시 후 에러 메시지 전송
});
app.listen(8080, ...);

제일 위에 const route = require('./routes.js');를 했는데 이 부분이 route 변수에 아까 module.exports로 export했던 router을 연결하겠다는 뜻입니다. app.use로 라우팅을 하는 것의 장점은, 그룹화가 쉽다는 겁니다. app.use('/category', route1); 이런 코드가 있으면 route1에 있는 라우터들은 모두 category 주소 아래에 그룹화됩니다. route1에 router.get('/javascript', 콜백)라는 코드가 있다면, 자동으로 '/category/javascript' 주소로 연결됩니다.

에러 처리 부분을 볼까요. 먼저 위의 라우터에서 하나도 일치하는 라우트가 없을 때 404 에러를 브라우저로 돌려줍니다. 404 에러는 서버의 에러가 아니기 때문에 딱히 서버에 기록하거나 하지는 않습니다.

그 아래는 서버 에러를 처리하는 부분입니다. 일반 app.use에 비해 err 매개변수 하나가 더 있습니다. 바로 next(err)로 넘겨줬던 에러가 최종적으로 도착하는 부분입니다. next(err)가 호출되는 순간 다른 app.use는 모두
건너뛰고 바로 err 매개변수가 있는 app.use로 넘어옵니다. 이 부분이 없으면 next로 에러를 넘겨주었을 시 처리할 부분이 없어 서버가 죽어버립니다. 에러를 기록하고 브라우저에 서버에서 에러가 발생했다고 알려줍니다.

다음 시간에는 익스프레스 템플릿에 대해 알아보겠습니다!

조회수:
0
목록
투표로 게시글에 관해 피드백을 해주시면 게시글 수정 시 반영됩니다. 오류가 있다면 어떤 부분에 오류가 있는지도 알려주세요! 잘못된 정보가 퍼져나가지 않도록 도와주세요.
Copyright 2016- . 무단 전재 및 재배포 금지. 출처 표기 시 인용 가능.
5만명이 선택한 평균 별점 4.9의 제로초 프로그래밍 강좌! 로드맵만 따라오면 됩니다! 클릭

댓글

11개의 댓글이 있습니다.
3년 전
Method-override는 REST API에서 PUT과 DELETE 메소드를 사용할 수 있게 합니다


이 부분은 무슨 뜻이죠? 원래 REST API는 PUT과 DELETE를 사용하지 않나요?
3년 전
form에서는 못 써서 그렇습니다.
4년 전
안녕하세요 책 구매해서 공부하는 중인데 도저히 해결이 안 되는 오류가 있어서 문의 드립니다. ㅠㅠ...
app.get('/', (req, res, next) => {
console.log('GET / 요청에서만 실행됩니다.');
next();
}, (req, res) => {
throw new Error('에러는 에러 처리 미들웨어로 갑니다.')
});
app.use((err, req, res, next) => {
console.error(err);
res.status(500).send(err.message);
});
이 부분에서 에러 처리 미들웨어가 4개의 매개변수를 인식하지 못합니다
err를 리퀘스트로, req를 리스폰스로, res를 next로 인식하고 마지막 next는 무엇인지 인식하지 못합니다 그래서 status 메서드나 send 메서드도 인식을 못해요,,, 3개의 매개변수만 들어올 수 있는 것처럼 오류가 나는데... 왜 이런 걸까요...?
4년 전
안녕하세요 책도 구매해서 보긴했는데 뭔가 아직 잘 모르겠어서 이 포스팅과관련이 있는지는 모르겠지만 질문하나 드려도 될까요?
글을 보는 화면에 수정버튼이 원글의 작성자에게만 보이게 하려고 하는데 이 화면(ejs)을 렌더링하는 파일이랑 로그인관련 자바스크립트파일이랑 데이터를 주고받아야 렌더링하는 파일이 로그인정보를 이용해서 화면을 사람마다 다르개 처리하여보여줄수 있을거같은데
이런건 어떻게 해야하나요..? 그냥 파일간 데이터전달만하면될거같은데 잘모르겠습니다..
4년 전
제 책에 팔로잉한 사람한테는 언팔로우 버튼을 표시하고, 아닌 사람한테는 팔로우 버튼을 표시하는 부분이 있습니다. 그걸 응용하셔서 원글 작성하면 수정 버튼을 보이고, 아니면 안 보이면 됩니다.
4년 전
안녕하세요 제로초님 혹시 app.use()는 보통 어떤때에 많이 쓰나요? 공식문서에서는 app.method(path, handler) 이렇게 되가지구 method 부분에는 http method가 들어간다라고 되어있더라구요 app.get app.post app.options 이런식으로 쓰는구나 이해했는데 app.use는 어떤때에 어떻게 써야하는지에 대해서는 찾질 못해서요 여러 예시들을 보니까 라우팅? 할때 쓰는 경우도 있는 것 같던데 막 써도 되는건가요?
4년 전
app.use는 기본적으로 모든 라우터/모든 메서드에 적용되는 미들웨어를 넣을 때 사용됩니다. 특정 주소에만 적용하고 싶으시면 app.use(주소, 미들웨어) 하시면 됩니다.
5년 전
라우터라는 것은 app.get("/", 라우트핸들러함수) \u003c- 이부분 전체를 뜻하는건가요? 라우팅코드라고 생각을 하고 있었는데 명확한 정의설명좀 부탁드리겠습니다
5년 전
네 전체를 의미합니다. 라우트핸들러함수는 미들웨어라고 불리고요.
6년 전
안녕하세요! 마지막 소스코드에서

const route = require('./route.js');
...
app.use('/', route);

이 ...가 생략한 내용이 const app = require('express'); 인가요??
const route = require('./route.js');
app.use('/', route);
app.use(compression());
이런식으로 붙여쓰니 app이 정의되어 있지 않다는 에러가 발생하네요..ㅠㅠ 이해를 잘 못한건지 모르겠습니다. 도움 부탁드려요
6년 전
const compression = require('compression');
const cors = require('cors');
const express = require('express');
const app = express();

가 생략되었습니다.
7년 전
"next(err)" 부분이 코드 안에서 보이지 않네요!!!!
7년 전
아아 다음 부분을 뜻하는 말이었습니다!
"참고로 에러가 발생했을 때는 next(에러)처럼 next의 인자로 error 정보를 넣어 라우팅 부분으로 넘겨줍니다. 안에
에러 객체를 넣어주었을 때는 상황이 조금 다릅니다. 그 에러는 라우팅에서 처리합니다. 이 글 제일 아래 나와있습니다."
7년 전
안녕하세요?
ZeroCho님 블로그로 공부하고 있는 사람입니다.
오류처리시 텍스트만 나오지 말고 특정 페이지(html 파일)로 이동하게 하고 싶은데 방법을 몰라서 여쭤봅니다.
7년 전
똑같이 res.sendFile이나 res.render로 html이나 템플릿 파일을 렌더링해주면 됩니다!
7년 전
res.sendFile로 진행하고 있는데 다음과 같이 하였는데 안되서 질문합니다.

res.status(404);
res.sendFile(path.join(__dirname, 'html', 'error.html'));

res.status(404).sendFile(path.join(__dirname, 'html', 'error.html'));
7년 전
폴더 구조랑 파일 경로를 알아야될 것 같습니다.
7년 전
에러 로그는 다음과 같습니다.
Error: Can't set headers after they are sent.
at SendStream.headersAlreadySent


파일 구조는 다음과 같습니다.

메인 디렉토리
html 디렉토리
error.html
server.js


바쁘신대 신경써주셔서 감사합니다.

7년 전
저 에러는 res.sendFile이나 res.send 등을 하나의 라우터에서 두 번 이상 호출했을 때 발생하는 에러입니다. 혹시 응답을 두 번 이상 보내지 않는지 체크해보세요
7년 전
파일분리에 대해서 설명해주셔서 생각난건데 require로 불러들이는 부분이 모든 라우터에너 써야하는건가요? 만약 그렇다면 require로 불러들이는 부분을 따로 파일로 지정하고 그 파일만 불러오는 코드를 짜려면 어떻게 해야할까요???
7년 전
그렇게 하기는 힘듭니다. 하나의 파일을 하나의 모듈이라고 생각하시면 됩니다. 하나의 모듈에 필요한 다른 모듈들을 require하는 것이라, 다른 파일로 분리하는 순간 다른 모듈이 되어버립니다.
8년 전
코드 부분에 설명이 적혀있는걸로 보아, 게시물 내용의 설정이 약간 꼬인 것 같습니다!
8년 전
백알못인데요..
server.js 나 route.js 파일에 app.use(express.static(path.join(__dirname,'views'))); 부분이 없는데 제대로되는건 왜인거죠?
8년 전
express.static은 정적 파일들의 경로를 설정하는 거라서, view랑은 상관 없습니다. 위의 예시에서는 그냥 res.sendFile로 직접 html 파일을 보내주고 있습니다.
8년 전
그럼 강좌4 - Express 프레임워크 의 Routing 코드부분의 app.use(express.static(path.join(__dirname, 'html'))); 이 부분이 없어도 app.get('/', function (req, res) { res.sendFile(path.join(__dirname, 'html', 'main.html')); }); 이 정상작동하는건가요??
8년 전
없어도 됩니다. 대신 있으면 주소에 /main.html 이렇게 쳐도 접근 가능해집니다.
8년 전
오 확인했습니다 잘알겠습니다 감사합니다 제로님 이런질문들은 댓글로해도 괞찮나요?
8년 전
네 괜찮습니다!