Search
▪️

Understanding Validation

Why Should We Use Validation?

사용자가 Form에 맞춰서 Input을 Server로 넘기고, Server에서는 이 정보가 맞다면 데이터베이스와 Interact하여 정보를 사용자에게 뿌려준다.
이 과정 중 Form에서 Server로 넘길 때, 올바른 형식으로 쓰여진 Input인지 검증하는 과정이 필요하다. 이런 Validation 과정을 Controller와 Middleware를 통해서 작업한다. 만일 이 Validation이 성공적이지 않다면 사용자의 Input을 Reject한다.

How to Validate Input?

Validation은 JavaScript의 도움으로 Client-Side에서 수행할 수 있다.
더 구체적으로 말하면, 사용자가 Typing하는 Input에 대해서 사용자가 Form에서 작업하는 동안 Validation을 수행할 수 있다. 실시간으로 Input 값에 대한 Error가 있는지 없는지 볼 수 있는 이유는, JavaScript로 DOM을 Runtime동안 변화시킬 수 있기 때문이다.
즉, Input이 Server로 넘어가지 않았음에도 Client 단에서 바로 바로 검증을 하여 Error의 유무를 볼 수 있는 것이다.
이렇게 Client 단에서 검증을 하는 것은 UX 측면에서 굉장히 좋아질 수 있지만, 단점도 있다. 바로 해당 코드들이 Browser에서 작동하기 때문에, 이 코드들을 볼 수 있을 뿐 아니라 조작하거나 무력화할 수도 있다.
즉, Client에서 제공하는 Validation은 Secure를 위한 것보다는 UX를 위한 것이라고 볼 수 있다.
위와 같이 Client-Side에서 수행하는 Validation은 Secure보다는 UX적인 것이라고 볼 수 있다고 했는데, 따라서 이런 Validation들은 Optional하다.
반대로, Client-Side에서 Validation을 하여 가져온 데이터들이 진짜로 적절한 데이터들인지 Server-Side에서도 Validation을 해줘야 한다. 이 코드들은 사용자들에 의해 보여지는 코드들이 아니기 때문에, 쉽게 조작할 수 없다. Server-Side에서 치뤄지는 이런 Validation들은 Requirement이다. 반드시 수행해야 하는 것이다.
물론 Server-Side Validation이 아니라 MongoDB처럼 데이터베이스에서 Validation을 수행할 수도 있다.
하지만 이런 과정들 역시 Optional한 것이다. Server-Side에서 잘 짜여진 Validaiton Code가 있다면 꼭 필요한 과정은 아니기 때문이다. (이미 잘못된 데이터들은 Server-Side에서 걸러졌을 것이기 때문이다.)
주로 다루게 될 것은 두 번째인 Server-Side Validation일텐데, 이 때 Validation이 실패하면 이 사실을 사용자에게 알려야 한다.
여기서 중요한 것이, Validation Fail을 알리기 위해서 Page를 Refresh하게 되면 사용자의 Input이 모두 날아가기 때문에 최악의 UX가 될 수 있으므로 주의해야 한다.

Setup & Basic Validation

Server-Side에서의 Validation은 express-validator라는 Third Party Package를 사용한다. (이제 Session의 Flash는 자주 쓰지 않는다.)
express-validator은 몇 Sub Package들로 구성되어 있기 때문에, 필요한 Sub Package만 Import하여 쓴다. express-validator/check와 같이 말이다.
Package로부터 특정 이름의 Object를 뽑아서 쓸 수 있다. 이를 Destructuring이라고 한다.
const { check } = require('express-validator')
위 과정을 Validating을 하려는 Router Script에서 선언하도록 한다. 그리고 Import한 check() Function을 Validating을 수행하려는 Routing Middleware에 포함시킨다. (Router에는 Request를 Handling하는 Middleware들을 많이 둘 수 있으므로)
check() Function은 Middleware를 Return한다. 인자는 Validate하려는 Field Name을 주거나 Field Name을 Array로 준다. Field Name이라고 함은, Input의 name 부분이 Field Name이 된다.
check() Function의 인자를 모두 주었으면, Chaining을 통해서 검증하려는 바의 Method를 수행하도록 한다. isEmail()을 Chaining으로 쓸 수 있다.
** 이외에도 isURL(), isFloat(), isString() 등등 많다. Official Document를 찾아본다.
** isAlphanumeric() 같은 경우에는 공백을 허용하지 않는다..!
위와 같이 Routing에서 Validator를 Middleware로 두었다면, Middleware에 대한 작업을 Controller에서 구현해야 한다.
Routing Script에서 check() Function을 Destructuring 했듯이, Controller에서도 validationResult를 Destructuring한다.
validationResult는 Routing Middleware에서 check() Function이 수행되면서 생겼던 모든 Error들을 얻을 수 있게 하는 Function이다.
Controller에 있는 Module중에서 validationResult값이 필요한 곳에 const errors = validationResult()하여 Error들을 추출한다. validationResult() Method의 인자는 request를 넣으면 된다.
validationResult() Method를 통해서 Error를 추출 했을 때, Error가 존재하지 않는다면 Redirect를 하지 않고 다시 Render를 한다. (Render전에 Status Code는 일반적으로 422로 둔다.) Validation에서 Redirect는 Request 성공 시에만 하는 것이 일반적이다.
** Test를 진행하려는데 Frontend의 Browser에서 Input의 Type에 맞춰서 Validation을 먼저 해버린다면, form의 novalidate라는 옵션을 주면 된다.

Using Validation Error Messages

validateResult() Method를 통해서 Error를 추출하여 보면 msg라는 필드에 Error Message가 존재한다. 이 Message 역시 Customizing이 가능하다.
이는 validateResult() Method가 수행되기 전의 Middleware인 check() Function의 Chaining을 통해서 구현할 수 있다.
withMessage()라는 Method를 통해서 Customizing이 가능하며, 해당 Method는 반드시 Validating Method 뒤에 따라야 한다.

Built-In & Custom Validators

express-validator를 찾아보면 더 많은 Built-In Validator들이 있다. (Official Docs 참고)
예를 들어, Email의 Validation을 검증할 때도 특정 Email에 대해서 Validate할 수 있다. 이는 check() Method에 Chaining을 한 Validator에 이어서 Chaining을 한다. custom() 이라는 Method를 이용한다.
custom() Method는 Callback Function을 인자로 받는다. Callback Function의 인자는 value와 {request}를 가진다. (request에서 더 많은 정보를 추출해야 할 수도 있기 때문...)
custom() Method의 Callback Function에서 throw new Error를 하게 될 시 해당 Error가 Message로 나타나게 된다.

More Validators

express-validator의 Validator를 Routing의 Middleware에 Multiple하게 둘 수 있는데, 이는 Array로 묶어도 되고 안 묶어도 된다. 다만, 묶었을 때의 Validator구나 하는 가독성을 더 높일 수 있다.
이 때 Validator를 꼭 Check만 쓸 필요 없이, 다른 Method들도 많이 존재하니 찾아보고 더 알맞는 것을 사용하는 것이 맞다.
Check의 경우 Field Name으로 주어진 것을 Cookie, Session, Header, Request 등등 모든 것들의 Field Name을 보고서 Validating을 하는데, 만일 Request의 Body에서만 Field Name을 추출하여 쓰고 싶다면 check() Method보다는 body() Method가 더 적합하다.
check() Method와 달리 body() Method는 오로지 Request의 Body만 신경 쓴다.
Validator뒤에 따르는 Chaining 인자로 isLength()를 통해 Validator에 존재하는 값의 길이에 대한 설정을 할 수 있다. 이 때, isLength의 인자는 JSON 형태의 Object를 준다.
모든 Validating마다 withMessage() Method로 Message를 정하는 Redundant한 방법을 쓰기 싫다면, body()나 check()의 두 번째 인자로 값을 주면, 해당 값이 Default Error Message가 된다.

Adding Async Validation

Promise는 JavaScript의 Built-In Object이다. 만일 Validating 도중 Promise를 쓸 일이 있고, 이에 대해서 Return 받은 Promise를 폐기해야 하는 경우 Reject를 할 수 있다.
return Promise.reject()
Promise.reject()라고 하는 Method는 Promise 내부에서 Error를 Throw하고, Promise 작업을 중단시킨다. reject() Method 내에 받는 인자는 Reject하여 Error를 던질 때, Error Message로 나타낼 값이다.
Reject 시에 Error을 Throw하기 때문에 Validator는 이 Error을 Catch하여 Message로 나타낼 수 있는 것 이다.

Sanitizing Data

Validator와 Chaining하여 쓰는 Method이고, 데이터를 특정 형태로 정리하여 보여준다. Sanitizing을 쓰게 되면, Validating을 하고 아무런 이상이 없다면, 데이터들을 정리하여 데이터베이스에 저장시킨다.
Email 같은 경우는 모두 소문자로 바꾸면서 정규화 하는 .normalizeEmail() Method를 쓰기도 하고, .trim() Method를 사용 시에는 전후로 잡혀 있는 공백에 대해서 모두 없애준다.

Validating Product Addition

Validating Product Editing