(5/6 ~5/19) The Web Developer 부트캠프 2022 강의 내용 정리
범위 : YelpCamp CURD ~ Express와 Mongo연결하기(섹션 41 ~43)
미들웨어는 요청에서 응답 사이에 실행되는 함수이고 요청 객체와 응답 객체에 접근 및 수정할 수 있다.
Express는 라우팅과 최소한의 기능을 갖춘 미들웨어 웹 프레임워크의 결합이다
즉, Express 앱은 미들웨어 함수 호출의 연쇄일 뿐이다
request logger middleware이다. 각종 Request 요청에 대한 간략한 정보들을 콘솔에 출력해주는 역할을 한다.
app.use(morgan('tiny'));
//dev 옵션
app.use(morgan('dev'));
//common 옵션
app.use(morgan('common'));
//tiny
GET / 200 9 - 3.296 ms
GET /favicon.ico 404 150 - 3.272 ms
//dev
GET / 304 4.349 ms - -
//common
::1 - - [18/May/2022:14:23:40 +0000] "GET / HTTP/1.1" 304 -
app.use((req,res,next)=>{}) 형태로 정의하며 next함수를 통해 다음 미들웨어 또는 라우트 핸들러를 호출한다.
app.use((req, res, next) => {
console.log('ThIS IS MY FIRST MIDDLEWARE!');
next();
});
app.use((req, res, next) => {
console.log('ThIS IS MY SECOND MIDDLEWARE!');
req.requestTime = Date.now();
next();
});
//미들웨어에서 요청이 들어온 시간을 요청객체의 requestTime에 넣어주어 라우트핸들러에서 접근할 수 있게 함
app.get('/', (req, res) => {
console.log(req.requestTime);
res.send('HOME PAGE');
});
라우트 핸들러 마지막에 미들웨어를 추가하여 어떤 요청에도 매칭되지 않을 경우 동작하도록 한다.
app.get(...)
app.get(...)
app.use((req,res)=>{
res.status(404).send("Not Found");
})
미들웨어에서 패스워드를 검사하여 일치하지 않는 패스워드일 경우 요청이 거부되도록 할 수 있다.
이를 토대로 특정 라우트에서 패스워드 확인을 하도록 할 수도 있다.
app.use((req, res, next) => {
const { password } = req.query;
if (password === 'secret') next();
res.send('Sorry YOU NEED A PASSWORD');
});
app.get(...)
const verifyPassword = (req, res, next) => {
const { password } = req.query;
if (password === 'secret') next();
res.send('Sorry YOU NEED A PASSWORD');
};
//라우트 핸들러 2번째 인자에 미들웨어를 추가해주면 된다.
app.get('/', verifyPassword, (req, res) => {});
상용구를 사용할 수 있게 해주는 툴이다.
view파일에서 <% layout('layouts/boilerplate') %> 로 선언하면
해당 view파일은 <%- body%> 여기에 들어가게 된다. 그렇게 되면 아래에
BEFORE , AFTER가 상용구가 되어 공통적으로 쓰일 수 있게 된다.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>BOILERPLATE!!!</title>
</head>
<body>
<h1>BEFORE</h1>
<%- body%>
<h1>AFTER</h1>
</body>
</html>
<% layout('layouts/boilerplate') %>
<h1>Edit Campground</h1>
<form action="/campgrounds/<%=campground.id%>?_method=PUT" method="POST">
<div>
<label for="title">Title</label>
<input type="text" id="title" name="campground[title]" value="<%=campground.title%>" />
</div>
<div>
<label for="location">Location</label>
<input
type="text"
id="location"
name="campground[location]"
value="<%=campground.location%>"
/>
</div>
<button>Update Campground</button>
</form>
<a href="/campgrounds/<%=campground.id%>">Back To Campground</a>
내장된 오류 핸들러가 앱에서 발생한 모든 오류를 처리한다.
오류 객체를 찾은 다음 상태 코드와 상태 메시지를 찾는다.
상태코드의 디폴트는 500이지만 바꿀 수 있다.
내장된 오류 핸들러만으로는 모든 오류를 처리할 수 없기에 다양한 유형에 대한
오류 핸들러를 추가하여야 한다.
예를 들면 Mongoose에서 유효성 오류가 발생한 경우 구문 오류나 인증 오류와는 다른 방법으로 처리해야한다.
app.use((err, req, res, next) => {
console.log('ERROR!');
res.status(500).send('Oh! Error!');
//내장된 오류 처리기를 사용하고 싶으면 next함수 호출 이 때 인수로 err를 전달해야한다.
//express는 next에 무언가를 전달하면 현재 요청을 오류로 간주하여 남아 있는 오류 처리에 대한 동작만 한다.
});
오류에 대한 상태 코드와 메세지를 정해주는 작업을 각기 다른 상황에서 발생하는 모든 오류에 다 처리하려면 번거롭기에 이를 클래스로 만들은 것
class AppError extends Error {
constructor(message, status) {
super();
this.message = message;
this.status = status;
}
}
const verifyPassword = (req, res, next) => {
const { password } = req.query;
if (password === 'secret') next();
throw new AppError('password required', 401);
};
app.use((err, req, res, next) => {
const { status = 500 } = err; //오류에 상태코드가 없을 경우 디폴트를 500으로 설정
const { message = 'Something Went Wrong' } = err;
res.status(status).send(message);
});
라우트 핸들러와 미들웨어에 의해 발동된 비동기 함수에서 반환된 오류의 경우에는
다음 함수로 전달하여 Express가 잡아내서 처리할 수 있게 해야 한다. 그렇지 않으면
오류 핸들러가 작동되지 않는다.
app.get('/products/:id', async (req, res, next) => {
const product = await Product.findById(id);
if (!product) {
return next(new AppError('Product Not Found', 404));
}
//next가 호출되더라도 next다음 코드의 실행은 중단되지 않기 때문에 return이나 else로
//정상 응답인 경우의 코드가 실행되지 않도록 해야한다.
res.render('products/show', { product });
});
비동기 함수에서는 모든 걸 try...catch 문으로 감싸야 한다.
비동기 함수일 때만 해당이 되고 다른 경우엔 Express가 처리하기에 예를 들어 new를 render할 땐
try...catch 문으로 감쌀 필요가 없다.
app.get('/products/:id', async (req, res, next) => {
try {
const product = await Product.findById(id);
if (!product) {
throw new AppError('Product Not Found', 404);
}
res.render('products/show', { product });
} catch (e) {
next(e);
}
});
try...catch 문을 모든 각각의 비동기 라우트 콜백 그리고 핸들러와 미들웨어 등에
일일이 추가하는 것은 번거롭기에 이를 해결하고자 wrapAsync라는 함수를 정의하여 사용한다.
이 함수는 함수를 전달받아 catch메소드를 체인하여 에러가 발생할 경우 next함수를 호출하도록 한다.
function wrapAsync(fn) {
return function (req, res, next) {
fn(req, res, next).catch((e) => next(e));
};
}
app.get(
'/products/:id',
wrapAsync(async (req, res, next) => {
const product = await Product.findById(id);
if (!product) {
throw new AppError('Product Not Found', 404);
}
res.render('products/show', { product });
})
);
Express 5부터 Promise를 반환하는 라우트 핸들러 및 미들웨어는 거부하거나 오류를 던질 때 자동으로 next(value) 호출 한다.
$ npm install express@next //최신 버전의 express v5 알파 버전 설치
express5에서 비동기 오류 처리 예시
app.get('/user/:id', async function (req, res, next) {
var user = await getUserById(req.params.id);
res.send(user);
});
모든 Mongoose 오류에는 name이라는 특성이 있다. 이를 활용하여 오류 처리 미들웨어에서 오류마다 handle에러처리 함수를 호출하게할 수 있다.
Mongoose 오류 종류
const handleValidationErr=(err){
console.dir(err)
return new AppError(`Validation Failed...${err.message}`,400)
}
app.use((err, req, res, next) => {
if (err.name === 'ValidationError') err = handleValidationErr(err);
next(err);
});
자바스크립트 유효성 검사 도구이다.