티스토리 뷰

사용 Node.js 라이브러리

🌝express-async-handler

Express.js에서 비동기식 요청 핸들러를 처리하기 위한 미들웨어(async error handleing middleware for Express)

Express.js의 기본 요청 핸들러는 예외 처리를 하지 못하는 경우가 많기 떄문에, 이를 해결하기 위해 만들어진 미들웨어 라이브러리이다. 이 라이브러리를 사용하면 예외 처리가 가능한 비동기식 요청 핸들러를 쉽게 작성할 수 있다.

  • async를 쉽게 사용할 수 있고, Request Handler를 처리하는데 공통적으로 오류처리를 할 수 있거나 간단하게 구현할 수 있다. 

1-1. request handler의 오류처리

Q. What is request handler in Express?
A. A request handler is a function that will be executed every time the server receives a particular request, usually defined by HTTP method. It accepts two parameters request and response.
  • promise().catch(next)
  • async function, try ~ catch, next

1-2. async request handler 

  • async의 비동기 처리는 매우 편리하지만, 매번 try ~ catch 구문을 작성하는 것은 귀찮고 실수하기 쉽다.
  • request handler를 async function 으로 작성하면서 try- catch, next를 자동으로 할 수 있도록 구상한 아이디어다.

사용법:

const asyncHandler = require('express-async-handler')

express.get('/', asyncHandler(async (req, res, next) => {
	const bar = await foo.findAll();
	res.send(bar)
}))

without express-async-handler

express.get('/',(req, res, next) => {
    foo.findAll()
    .then ( bar => {
       res.send(bar)
     } )
    .catch(next); // error passed on to the error handling route
})

async-handler 가 동작하는지 postman으로 확인해보기

🌝bcrypt 

해싱: 일방향성 암호화의 일종으로, 입력된 데이터를 고정된 길이의 문자열로 바꾸는 것

비밀번호를 해싱(hashing)하기 위한 라이브러리이다. 해싱 알고리즘 중 하나인 bcrypt 알고리즘을 사용하며, 안전하고 강력한 해싱을 제공한다. 

  • 주로 사용자 비밀번호를 해싱하여 데이터 베이스에 저장하는 용도로 사용된다.
  • 비밀번호를 검증할 때에는 입력된 비밀번호를 해싱하여 디비에 저장된 해시값과 비교한다. 이를 통한 원본 비밀번호를 보호할 수 있다. 

 


몽구스(ODM)란? 

시퀄라이즈(관계형 데이터베이스 a.k.a. RDBMS와를 다룰 때 유용한 도구, SQL 쿼리를 자동으로 생성해주는 등의 기능을 제공한다. SQL 데이터베이스를 다루는 라이브러리므로 NoSQL과는 호환되지 않음)와 달리 릴레이션(DB의 `테이블`과 유사개념)이 아닌 도큐멘트를 사용하므로 ORM이 아니라 ODM(Object Document Mapping)이라고 불린다.

 

Q. 몽고디비 자체가 JS인데 왜 굳이 JS객체와 매핑해서 사용할까?

A. 몽고디비에 없어서 불편한 점들을 몽구스가 보완해주어 서버단에서 NoSQL DB를 프로그래밍하는데 최적화하기 때문이다.

 

몽고디비에는 없던 `스키마`라는 개념이 생김(DB의 스키마를 말하는 것)

원래 몽고디비에는 테이블 개념이없어(collection) 자유롭게 데이터를 넣을 수 있지만, 이 자유분방함이 오히려 독이 된다.

e.g.) 실수로 잘못된 자료형의 데이터를 넣거나 다르 도큐멘트에는 없는 필드의 데이터를 넣을 수도 있기 때문이다.

 

몽구스는 몽고디비에 데이터를 넣기 전 노드 서버 단에서 데이터를 한 번 필터링하는 역할을 해준다.

또한, 몽고디비 쿼리를 프로미스 객체로 만들어주어 강력하고 가독성이 높은 쿼리 빌더를 지원하는 것도 장점

*몽구스 ODM 문법 자체가 몽고디비 SQL문이기 때문에 몽고 DB의 종합적인 지식이 필요함

 

all ref: 인파 dev님 mongoose 사용법 정리 


몽구스 쿼리 메서드를 사용하여 User 모델 API 만들기

만들어놓은 모델 User과 Note를 가져온다. express의 기본 요청 핸들러는 예외 처리를 하지 못하는 경우가 많기 때문에 express-async-handler를 가져와서 사용해준다.

controllers/userController.js

const User = require("../models/User");
const Note = require("../models/Note"); // 컨트롤러 내부에서 참조할 수도 있으니까 가져온다.
const asyncHandler = require("express-async-handler");
const bcrypt = require("bcrypt");

// @desc GEt all users
// @route GET /users
// @access Private

const getAllUsers = asyncHandler(async (req, res) => {
  //find, select, lean 메서드는 mongoose의 메서드
  const users = await User.find().select("-password").lean();
  if (!users) {
    return res.status(400).json({ message: "No users found" });
  }
  res.json(users);
});
  • get 요청을 유저 컨트롤러에 만들어본 모습이다. User.find()를 처음봤을 때는 array.find()인줄 알았으나 select와 lean을 보고 JS 내장 메서드가 아닌 거 같았다. 당연하게도 User은 몽구스 스키마로 만들어진 모델이다. 그렇다면 `find(), select(), lean()`은 몽구스에서 제공하는 메서드(쿼리 메서드 or 체이닝 메서드 라고 불린다)이다. 
  • 코드 설명: find()로 User 모델을 조회하고, select("-password")로 조회된 결과에서 password 필드를 제외한 필드만 조회하고, lean()로 조회된 결과를 JS객체(POJO)로 변환한다. 
  • 마지막 줄의 `res.json(users)` => 에서 명시적인 상태 코드가 없는 이유: Express에서 명시적으로 상태 코드를 설정하지 않고 `res.json()` 또는 `res.send()` 메소드를 호출하면, 기본적으로 200 OK 상태 코드가 반환된다. 또한, 클라이언트 측에서도 서버에서 지정한 상태코드가 없을 시, 기본적으로 200 OK 상태 코드를 받게 된다.
  • JavaScript Object = POJO
Q. 조회된 결과를 왜 JS객체로 변환해야하나? 

A. `몽구스 쿼리`를 통해서 조회된 데이터는(다른 말로하면 `리턴값`이) 단순한 Plain Old JavaScript Objects(POJO)가 아닌 Mongoose Document 형태이기 때문이다. 
-> 다른 정보나 메서드를 사용하지 않는다면 굳이 `몽구스 도큐멘트`를 사용할 필요가 없다. 보통은 toObject() 메서드를 사용하여 POJO로 변환해도 된다. 하지만, lean() 메서드를 사용하면 훨씬 더 효율적이다.
*Mongoose Document > POJO => 용량 10배 이상의 차이를 가짐

toObject() : Query 결과를 몽구스 도큐멘트로 만들기 위한 과정을 거쳤다가 다시 POJO로 변환
lean(): 중간 과정없이 바로 POJO 반환 

Mongoose Document 가 포함하는 것들 

  • Change tracking
  • Casting and validation
  • Getters and setters
  • Virtuals
  • `save()`
추가 정보와 메서드를 포함하고 있어 Query 결과 값을 가지고 수정한 후, `save()` 메서드를 통해 수정된 내용을 DB에 반영할 수 있다.

e.g.) 

const findAndUpdateName = async (oldName, newName) => {
  const user = await User.findOne({ name: oldName });
	user.name = newName;
	const savedUser = await user.save();

  return savedUser;
};

 

몽구스 쿼리(유사 프로미스)와 exec()메서드

몽구스 메서드들은 유사 프로미스를 반환하여 프로미스 체이닝이 가능하게 만들어주는데, 이때, 반환되는 유사 프로미스를 몽구스에서 Query 라고 부른다. 대부분의 몽구스 메서드들이 쿼리를 반환하며 then()을 사용할 수 있게 한다(유사 프로미스라서). 하지만, 이 쿼리를 반환하는 메서드에 exec() 메서드를 사용하면 `쿼리`가 아니라 `온전한` 프로미스를 반환값으로 얻을 수 있다.  

+ 추가 설명: exec() 메서드는 몽구스의 쿼리 메서드에서 마지막으로 사용되는 메서드로, 실제 쿼리를 실행시키는 역할을 한다. 그리고 exec()는 콜백을 받아들일 수 있는 콜백 스타일로 실행할 수 있으며 그렇지않으면 프로미스 스타일로 실행된다. 따라서 쿼리 메서드를 호출한 뒤 exec()를 붙여주는 것은 해당 쿼리를 실행시키기 위한 명령어를 추가하는 것이다. 이를 통해 몽구스에서 지원하는 콜백 스타일과 프로미스 스타일 중에서 원하는 방식으로 결과를 받을 수 있다. 

 

그러나 만약에 lean() 메서드를 사용한다면, exec()를 생략하고 바로 프로미스를 반환받을 수 있다. lean()메서드는 쿼리 결과를 JS객체로 바꿔주어, 데이터를 더 가볍게 처리할 수 있게 해준다. 

 

> 몽구스에서 쿼리를 실행하면 Promise를 반환한다고 한다.

이렇게 되면 에러가 났을 때, stack trace에 오류가 발생한 코드의 위치가 포함되기 때문에 디버깅에 좋다. docs에서도 exec()의 사용이 권장된다.
const user = await User.findById(id).exec();
댓글