Search
▪️

Error Handling

Types of Errors & Error Handling

Errors are not necessarily the end of your app! ⇒ You just need to handle them correctly!
Error가 Throw 되었을 때, 실제로 Error가 검출된다. 이렇게 Throw되는 Error들은 실제로 Node Application에서 Technical한 Object이다. 즉, Error라는 것은 Node.js에 Built-In Object로 존재한다.
Error를 검출하기 위해, Test Code를 이용할 수도 있다. 이런 것들을 통해서 Error Catch를 할 수 있다. Error Catch를 통해서 수훨하게 Error Handling을 할 수 있다.
Error의 경우 Error가 Throw된 상황과 아직 Error가 Throw되지 않은 두 상황으로 나뉜다.
1.
Error가 이미 Throw된 상황이다.
Synchronous Code의 경우 try-catch를 통해서, Asynchronous의 경우 then()-catch()를 통해서 Error를 검출 할 수 있다.
이렇게 Error를 검출하고 나면, 직접 처리를 할 수도 있을 뿐 아니라 Express.js에 Built-In으로 존재하는 Mechanism을 통해서 처리를 할 수도 있다. 후자의 경우 Error를 Handling하는 Special한 Middleware이다. 이는 Error를 Catch하고 Response를 Return하는 기능을 한다.
2.
Error가 Throw되지 않은 상황이다.
일반적인 로직을 처리하다가, Error를 던져야하는 상황이 나타나면 Throw Error를 하거나 직접 Error를 Handling 하는 방법이 있다.
Error에 대한 처리를 했으면, 이 Error에 대해서 사용자와 Interact하는 방법도 여러가지가 있다.
1.
Error Page (e.g. 500 page)
2.
Intended Page / Response with error information
3.
Redirect

Errors - Some Theory

Error Catch를 하지 않으면 Application이 구동되다가 멈출 수도 있게 된다.
따라서 Error Catch를 해줘야 Application이 Crash되지 않고 계속해서 구동될 수 있다.
더 나아가서는 Catch한 Error도 처리를 해주면 좋다.
그렇다면 Express Validator를 사용할 때는 따로 Catch가 없었는데 Error가 있어도 구동되는 이유는? → Express Validator가 자동으로 Catch하도록 만들어졌기 때문이다.
비동기 Code에서 then() catch()를 할 때, catch()는 이전의 모든 then()에 대해서 Error를 Catch한다.
** mongoose에서 ObjectId값에 대해서 새로 만들어줄 때는, mongodb의 new mongodb.ObjectId($value)와 달리 mongoose.Types.ObjectId($value)로 이용한다.

Returning Error Pages

Express Validator로 Validating할 때, validationResults를 통해서 추출한 Error가 있을 때만 Error Page를 Return하는 것이 아니라, 다른 Error들에 대해서도 Error Page를 Return하는 것이 좋다.
즉, 중요한 동기 코드가 있다면 이에 대해서도 Error Catch에 따른 Page Returning을 하는 것이 좋고, 데이터베이스와 같이 비동기 Operation이 많이 들어간 곳에 대해서 catch()로 Chaining한 것들은 단순히 로그만 찍는 것이 아니라 Error Page Returning을 하는 것이 좋다.

Using the Express.js Error Handling Middleware

이제까지 then() catch()를 쓰면, catch()에서는 Error Throwing없이 그냥 console.log()만 찍거나, Error Page를 만들어서 해당 Page로 Redirecting만 해주었다.
조금 더 접근성을 높이기 위해서, 그리고 매 Error마다 Render를 통해서 Error Page 보이는 것보다, Error Page를 만드는 것이 조금 더 낫기는 하지만 여전히 많은 코드들을 재사용 해야 한다.
과연 더 좋은 방법이 있는가? → Yes!
Error Page를 만드는 것도 Error Page만 같은 것이지 Error Message는 모두 달랐다. 즉, 각기 다른 Catch에서 Error Message만 날릴 수 있도록 하는 것이다.
정확히 말하면, then() catch()의 catch()에서는 Error 검출 시 Error만 Throwing해주고, Middleware를 하나 두어 Throwing 된 Error를 처리할 수 있게 하는 것이다!
일반적으로 Catch에서 Error를 검출 시 다음과 같이 처리한다. 검출한 Error를 next()를 통해 Error Handling Middleware로 갈 수 있도록 설정한다.
const error = new Error($error) error.httpStatusCode = $codeNumber return next(error)
여기서 굉장히 중요한 점은, app.use()를 Routing으로 이용 시 Exact Pattern Matching이 아니라 Pattern Include만 되어도 Routing이 되었다. 따라서 위의 Error를 Handling하는 Middleware를 어느 위치에 둬야하는지 고민이 많이 될 것이지만, 전혀 고민 없이 Server를 구동하는 코드 바로 위에 Error Handling Middleware를 둬도 된다.
Error Handling Middleware는 말 그대로 Express.js에서 제공하는 Error를 Handling 하는 Middleware인데 이것이 가능한 이유는, 해당 Middleware는 특별한 Type의 Middleware이기 때문이다.
Error Handling Middleware는 다른 Middleware가 request, response, next처럼 세 가지 인자를 받는 것과는 달리 네 개의 인자를 받는다. error, request, response. next 이렇게 네 개를 받는다.
이렇게 인자가 네 개가 주어진 Middleware는 Express.js에서는 Error Handling Middleware라고 인식하게 된다. 인자가 단순히 네 개가 주어졌을 뿐인데 Error Handling Middleware라고 인식할 수 있는 이유는, Express.js에서는 next()에 인자로 error가 주어질 시 Error Handling Middleware로 이동하도록 설계 되었기 때문이다. (다른 일반 Middleware를 모두 생략하고 말이다.)
Error Handling Middleware에서는 Response를 Redirect를 하기도 하고, 설정한 httpStatusCode를 통해서 Response를 Render하기도 한다.
그렇다면 Error Handling Middleware가 여럿 존재하고, Error를 next()를 통해서 Throwing했을 때는 어떻게 처리 되는가? → 일반적인 Middleware처럼 위에서 아래로 작동하게 된다.

Using the Error Handling Middleware Correctly

Error Handling 시에는 무한 루프에 빠지지 않도록 조심해야 한다.
Error Handling 한답시고 Routing 잘못 건드리면, Error 돌리고 Error Handling Middleware에서 Routing하고 다시 Error 터지고 Error Handling Middleware에서 Routing하고 과정을 반복하게 된다.
이는 대부분 Error Handling Middleware가 Error Handling으로 Routing을 하면서 생기는 증상인데, 따라서 Error Handling Middleware에서는 Routing보다는 Render를 사용하는 것이 이런 현상을 더 방지할 수 있는 방법이다.
또한 Synchronous와 Asynchronous에 따라서 Error 검출하는 방식이 달랐듯이, Error를 Throw하는 방법도 다르다.
Synchronous의 경우 throw Error('$error')와 같이 Throw하면 되지만, Asynchronous는 해당 코드가 먹지 않는다. Asynchronous는 next($error)로 Throw하게 된다.

Status Codes

Status Code들은 Browser에게 보내는 일종의 Extra Information이고, Browser에게 보냄으로써 Operation이 성공했는지 실패했는지 Browser가 이해할 수 있도록 돕는다.
이런 Status Code들은 Error가 발생 했으면, 어떤 Error가 발생했는지, Client에서인지 Server에서인지, 왜 발생했는지 등을 알 수 있게 해준다.
Status Code의 Default 값은 200번이다.
1.
2xx ⇒ Success Code
200 : Operation Succeeded
201 : Success, Resource Created
2.
3xx ⇒ Redirect Code
301 : Moved Permanently
3.
4xx ⇒ Client-Side Error
401 : Not Authenticated
403 : Not Authorized
404 : Not Found
422 : Invalid Input
4.
5xx ⇒ Server-Side Error
500 : Server-Side Error
이렇게 위 상황에 맞춰서 Response의 Status Code를 할당하여 Browser에게 보내는 것 역시 중요하다.
예를 들면, Creating 같은 경우 201, Redirecting의 경우 301과 같이 말이다. (Redirect에 대해서 조금 언급하자면... REST API를 쓰게 되면 html을 Return하는 것이 아니라 Data만을 Return하게 되므로 201의 Status Code를 갖게 된다.)
기본 값은 200이고, response.redirect($where) 시에 자동으로 301의 Status Code를 갖는다. 따라서 이외의 201, 404, 422, 500 등의 Code들을 적절히 할당 해주면 된다.
Login 실패와 같이 Authentication Fail의 경우, 수동으로 401 Status Code를 갖게 한다. 이에 해대서 Login 실패 시 Redirect를 하게 되면 401위에 301 Code로 덮어 쓰게 된다. 따라서 굳이 이런 경우에는 401을 먼저 할당하지 않아도 된다. (만일 REST API를 이용하게 되면 Server에서 Route를 하지 않게 되므로 이 때는 401에 대한 처리를 해줘야 한다.)
이런 Status Code들은 Browser에서 별도로 Code정보를 활용하여 추가 기능들을 제공하게 할 수 있다. (애초에 그렇게 구현 되어 있다. 크롬의 경우 200번의 Status Code가 아닌 것들을 Detect하여 사용자에게 보여주고 특정 Code마다 특정 기능들을 내부적으로 수행하게 된다.)
또한 REST API를 이용하는 경우, Status Code에 따라서 매 새로운 Page를 Render하지 않아도 되게 할 수 있다.
이런 Status Code들은 Request가 실패하여 Response를 못 받았거나 App이 Crash되었다는 것이 아니라, 어떤 문제가 생겼을 때 해당 문제의 정보를 Client에게 추가적인 정보를 주는 역할을 한다.
더 많은 Status Code는 HTTP STATUS CODE Document를 확인하도록 한다.
Status Code는 REST API에서 더 많이 중요하게 작용한다.