Node.js/Express Mongoose(몽구스) Middlewares(미들웨어)/Statics(스태틱) 함수
27 May 2021이전 포스트에서 이어짐
우리가 Model
을 이용해 document
를 저장하거나 수정할 때, 각 필드에 여러 작업들을 해야할 때가 있다.
예를 들어 사용자를 생성할 때 e-mail을 조회 한다던 지, 패스워드를 암호화 한다던 지, 이전 코드에서 hashtags
를 파싱하기 위해 문자열 함수들을 사용한다던 지 등의 사전 작업들을 할 수 있다.
이런 코드들은 사실 DB에 저장하기 전에 수행하는 데이터 전처리나 추가적인 유효성 검사같은 것이므로 컨트롤러 로직 안에 불필요하게 복붙하고 다니며 지저분하게 만들 필요없이 몽구스가 스키마 단에서 처리해 주는 기능을 제공한다.
→ https://mongoosejs.com/docs/middleware.html
Middleware (also called pre and post hooks) are functions which are passed control during execution of asynchronous functions. Middleware is specified on the schema level and is useful for writing plugins.
미들웨어(Middleware, pre, post hook)는 비동기 함수를 실행하는 동안 제어가 전달되는 함수다. 미들웨어는 스키마 수준에서 지정되며 플러그인 작성에 유용하다.
즉, 우리가 DB에 쿼리할 때 스키마별로 작성해 둔 Middleware들이 쿼리 실행 앞, 뒤에 실행되어 일련의 작업들을 수행한다.
또한 이 Middleware들은 스키마로 모델을 생성(정의)하기 전에 선언해야 한다.
☄ Pre hook 사용해서 hashtags 파싱하기
이전에 비디오를 생성하고 저장할 때 hasgtags
를 파싱하던 방법이다.
// controllers/videoController.js
// ... 생략
await Video.create({
title,
description,
hashtags: hashtags
.split(",")
.map((word) =>
word.trim()[0] === "#" ? `${word.trim()}` : `#${word.trim()}`
),
});
위 로직을 미들웨어로 옮길 수 있다.
// models/Video.js
// ... 생략
videoSchema.pre("save", async function () {
console.log(this);
this.hashtags = this.hashtags[0]
.split(",")
.map((word) => (word.startsWith("#") ? word : `#${word}`));
});
위 미들웨어는 save
미들웨어로 pre
메소드를 사용해 save
되기 전, 비동기 함수를 호출시킨다.
안에 작성한 this
키워드는 저장될 document
객체를 담고 있다.
- 주의할 점
위에서 화살표 함수를 안쓰고 굳이
fucntion
키워드를 사용해 함수를 작성했는데, 왜 인지는 모르겠지만 화살표 함수로 작성하면this
키워드를 사용 시 에러가 뜬다. 에러메세지도 컨트롤러의 create에서 발생해 찾기 어려웠다…
- 이유
주의할점은 this를 통해 해당 메서드를 불러온 객체의 값을 이용해야 하는데 화살표 함수를 사용하게 되면 lexical this를 사용하게 되어 해당 메서드를 이용하는데 불편하게 됩니다.
→ https://darrengwon.tistory.com/415
☄ 모델에 statics 함수 등록해서 hashtags 파싱하기
그런데 한 가지 문제가 있다. save
에 대한 pre
hook 미들웨어를 사용해 비디오 생성 시 자동으로 hashtags
를 파싱하도록 했다. 하지만 비디어 수정 후 저장은 또 다른 문제다.
// controllers/videoController.js
// ... 생략
export const postEdit = async (req, res) => {
const { id } = req.params;
const { title, description, hashtags } = req.body;
const video = await Video.exists({ _id: id });
if (!video) {
return res.render("404", { pageTitle: "Video Not Found" });
}
await Video.findByIdAndUpdate(id, {
title,
description,
hashtags: hashtags
.split(",")
.map((word) =>
word.trim()[0] === "#" ? `${word.trim()}` : `#${word.trim()}`
),
});
return res.redirect(`/videos/${id}`);
};
위의 경우 모델의 findByIdAndUpdate
함수를 사용해 조회와 수정을 동시에 수행하도록 했다. findByIdAndUpdate
은 내부적으로 findOneAndUpdate()
를 호출한다.
하지만 findByIdAndUpdate
함수에서는 등록한 pre
또는 post
save()
hook이 작동하지 않는다.
Pre and post
save()
hooks are not executed onupdate()
,findOneAndUpdate()
, etc.
또한, 따로 findOneAndUpdate()
에 대한 hook을 만들려 해도 수정하려는 document
에 접근할 수 없다.
You cannot access the document being updated in
pre('updateOne')
orpre('findOneAndUpdate')
query middleware.
따라서 우리의 비디오 모델의 경우, hashtags
를 파싱하도록 커스터마이징한 statics
함수를 모델에 등록하는 방법을 사용하는 것이 매우 좋다.
즉, Video
모델에서만 처리해야 하는 로직들을 statatics 함수로 만들어 사용해 모듈화
// models/Video.js
// ... 생략
videoSchema.static("formatHashtags", (hashtags) =>
hashtags
.split(",")
.map((word) =>
word.trim().startsWith("#") ? word.trim() : `#${word.trim()}`
)
);
formatHashtags
라는 이름의 statics
함수를 videoSchema
에 등록하는 방법이다.
그러면 Video
모델에서 다른 모델 함수와 동일하게 사용할 수 있다.
// controllers/videoContoroller.js
// ... 생략
export const postEdit = async (req, res) => {
const { id } = req.params;
const { title, description, hashtags } = req.body;
const video = await Video.exists({ _id: id });
if (!video) {
return res.render("404", { pageTitle: "Video Not Found" });
}
await Video.findByIdAndUpdate(id, {
title,
description,
hashtags: Video.formatHashtags(hashtags), // 수정
});
return res.redirect(`/videos/${id}`);
};
export const postUpload = async (req, res) => {
const { title, description, hashtags } = req.body;
try {
await Video.create({
title,
description,
hashtags: Video.formatHashtags(hashtags), // 수정
});
return res.redirect("/");
} catch (err) {
console.warn("Video.create error: ", err);
return res.status(404).render("upload", {
pageTitle: "Upload Video",
errprMessage: err._message,
});
}
};