백엔드 개발(fin)

이찬영·2024년 11월 7일

미들웨어

express는 request와 response사이에 위치하여, 어떠한 작업을 수행하는 함수를 미들웨어라고 한다. 즉, express는 클라이언트로 부터 request를 받아, 미들웨어를 거치며 만들어진 response를 반환한다.

next

# next
function greeting(req, res, next) {
	next();
  	res.json({message : "Hello World!"});
};

라우트 하나에 하나의 미들웨어만 사용할 수 있는것은 아니다.
그래서 하나의 미들웨어를 거쳤을때, 다음 미들웨어로 넘어가기 위한 수단이 필요한데, 그게 바로 파라미터에 있는 next이다. next는 다음 미들웨어 함수를 가리킨다.
예시와 같이 next()를 해주면 다음 미들웨어로 넘어갈 수 있는것이다.
미들웨어는 미들웨어를 아규먼트로 넘겨주는 순서에 따라 순서가 정해진다.

function meeting (req, res, next) {
	console.log('오!');
  	next();
};

function waving (req, res, next) {
	console.log('손을 흔든다');
  	next();
};

function greeting (req, res, next) {
	res.json({message : "Hello World!"});
};

app.get('/hello', waving, meeting, greeting);
// 손을 흔든다 => 오! => {message : "Hello World!"}

이 처럼 아규먼트를 넣은 순서에 따라 미들웨어가 실행된다.

하지만 코드가 많아지고 미들웨어가 많아지다 보면 일일히 적기 비효율적일 수 있다.
그래서 있는것이 app.all()과 app.use()이다.

app.all()

app.all()은 특정 결로에 대해 모든 HTTP메소드 요청을 처리할때 사용한다.
주로 라우팅 핸들러를 틀정 경로의 모든 메소드에 대해 하나로 정의하고 싶을때 사용한다.

app.all('/test', (req, res) => {
	res.send('이 경로는 모든 메소드에 응답한다.')
});

위 코드에서는 /test로 들어오는 모든 http메소드에 대해 동일한 핸들러가 실행된다.
한마디로, app.all()은 특정 경로에 대해 모든 http메소드 요청에 대한 핸들러를 설정할때 사용한다.

app.use()

app.use()는 특정경로나 조건에 관계없이 '모든' HTTP메소드에 대해 미들웨어 함수를 적용할때 사용한다. 일반적으로 URL경로를 기준으로 미들웨어를 설정하고 싶을때 사용한다. 예를 들어 인증이나, 로깅등의 기능이있는데, URL경로에만 맞으면 모든 하위 경로에도 적용된다.

app.use('/api', (req, res, next) => {
	console.log('API호출됨');
  	next();
});

위 코드에서 /api경로 이하의 모든 요청에 대해 app.use()에 설정된 미들웨어가 실행된다.
한마디로, app.use()는 특정 경로 및 하위경로에 대해 모든 HTTP메소드 요청에 미들웨어를 설정할때 사용된다. (경로를 설정하지 않으면 전역적으로 적용된다.)

핸들러와 미들웨어의 차이

미들웨어

  • 중간을 처리하는 함수이다.
  • 요청 <=> 응답 주기 사이에서 다양한 작업을 수행한다. 예를들어, 로그 기록, 인증, 요청데이터 분석등
  • next()함수를 호출하여 다음 미들웨어나 라우트 핸들러로 제어를 넘긴다.

핸들러

  • 특정 라우트에 최종적으로 응답을 보내기 위한 함수이다.
  • 요청을 마무리 하고 클라이언트에 응답을 보내는 역할을 한다.
  • next()함수를 사용하지 않고, res.send, res.json등을 통해 응답을 보낸 후 주기를 끝내는게 일반적이다.
  • 특정 http 메소드와 경로에 대해 설정된다.

req, res

하나의 리퀘스트가 여러 미들웨어를 지나가게 되어있다면, '모든' 미들웨어에서 같은 req객체와 res객체를 사용하게 된다. 즉, 객체에 원하는 값을 넣어 다음 미들웨어로 전달할 수 있는것이다.
마지막 미들웨어가 아니더라도, res.json()을 통해 중간에 위치한 미들웨어에서도 리스폰스를 전달할 수 있다. 예시로 중간 미들웨어에서

...
req.user = 'Lee';
next();
...

로 넘겨주게 된다면, 이 req객체에 'Lee'라는 user가 저장되고, 다음 미들웨어나 라우트핸들러에서 사용할 수 있게 된다.

내장 미들웨어

모든 경로에 대한 리스폰스와 리퀘스트를 대상으로 실행하는 목적이 대부분인데, 그래서 경로를 생략한 app.use()메소드에 미들웨어를 넘겨주는 방식으로 많이 사용한다.

express.json() : req.body에 담긴 json을 객체로 불러오기 위해 사용하는 미들웨어
ex ) app.use(express.json());

express.urlencoded() : content-type 헤더가 application/x-www-form-urlencoded의 리퀘스트를 처리하기 위해 사용하는 미들웨어인데, 이 미디어타입은 주로 html의 입력폼을 통해 서버로 데이터를 전송할때 사용한다. 미들웨어의 아규먼트로는 extended속성과 boolean값이 필요한데, 보통 true를 사용한다.
ex ) app.use(express.urlencoded({extended : true}))

express.static() : 이 미들웨어는 서버의 파일을 서버 외부로 전달하기 위해 사용하는데, 예를들어 public디렉토리에 example.png, index.html 파일이 있다면, app.use(express.statuc('public')) <= 파일이 위치하는 디렉토리를 아규먼트로 넘겨준다.

서드파티 미들웨어

cookie-parser : 리퀘스트로 전달받은 쿠키를 쉽게 파싱할 수 있게 하는 미들웨어이다.
쿠키는 리퀘스트 객체의 headers속성에서 확인할 수 있는데, 헤더의 Cookie속성을 문자열로 확인할 수 있는데, 이것을 사용하면 리퀘스트 객체의 속성에서 req.cookies객체로 확인할 수 있다.

import cookieParser from 'cookie-parser';

app.use(cookieParser());

morgan : 서버로 들어온 리퀘스트를 로그로 남기는 미들웨어이다.
app.use()의 아규먼트로 morgan()객체를 넘겨주고, morgan객체의 아규먼트로 리퀘스트에 대한 로그를 어느정도로 남길지 결정하는 옵션을 넣어주면 된다.
app.use(morgan('tiny')); <= 최소한의 정보를 남기는 옵션

라우트 중복 제거하기

...
const app = express();

app.route('/products')
.get((req, res) => {
	res.json({message : "목록보기"});
})
.poast((req, res) => {
	res.json({message : "추가하기"});
});

app.route('/products/:id')
.patch((req, res) => {
	res.json({message : "수정하기"});
})
.delete((req, res) => {
	res.json({message : "삭제하기"});
})
...

이런식으로 app.route('/path')를 사용하면 중복되는 라우트경로를 처리할 수 있다.

라우터만들기

하지만 중복되는 경로를 하나로 묶어 라우터관리에는 편하겠지만, 여전히 큰 하나의 app객체에 여러개가 연결되는 구조이다. 라우터가 늘어나면, 하나의 파일에서 관리해야하는것에는 변함이 없다.
그렇기에 라우터를 직접적으로 app객체에 연결하는것이 아니라 같은 여러 라우트를 묶어서 하나의 모듈로 만들고 그렇게 묶인 여러 모듈을 app객체에 연결하는 방식을 많이 사용한다. 이럴때 사용하는것이 router라는 객체이다.

const exampleRouter = express.Router(); // router 객체 생성

exampleRouter.route('/')
.get((req, res) => {
	res.json({message : "목록보기"});
})
.post((req, res) => {
	res.json({message : "추가하기"});
});

exampleRouter.route('/:id')
.patch((req, res) => {
	res.json({message : "수정하기"});
})
.delete((req, res) => {
	res.json({message : "삭제하기"});
})

app.use('/example', exampleRouter); // 

또 라우터 레벨에 따라 각자 다른 미들웨어를 설정해줄 수 있는데,

const userRouter = express.Router(); // router객체 생성

userRouter.use((req, res, next) => {
	console.log('userRouter에서만 사용하는 미들웨어');
  	next();
}) // use메소드 사용

userRouter.route('/')
.get((req, res) => {
	res.json({message : "user목록보기"});
})
.post((req, res) => {
	res.json({message : "user추가하기"});
});

const productRouter = express.Router(); // Router객체 생성

productRouter.route('/')
.get((req, res) => {
	res.json({message : "product목록보기"});
});


app.use('/user', userRouter); // userRouter.use()미들웨어 실행
app.use('/product', productRouter);  // 미들웨어 실행 X

express 프로젝트 구조와 모듈화

현재 app.js라는 하나의 파일에서만 라우트를 관리하고 app객체를 관리했었는데, 따로 정해진 정답은 없지만 나눠서 구조화시키는것이 훨씬 보기 편할것이다. 예를들어, app.use()등으로 사용했던 미들웨어는 middlewares라는 디렉토리에 모아서 함수화 시켜 import하여 사용하고, userRouter등과 같은 라우터는 routes라는 디렉토리를 만들어 라우트별로 관리하는 등. 보다 보기 쉽고, 쉽게 관리할 수 있도록 하는것이 좋다.

0개의 댓글