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
•