지난번에...
다시 컴퓨터에 앉았다. 저번 구상대로 모든 것을 싹 바꾸기로 했다. 일단 프론트에서 회원가입 양식을 받아서 값을 먼저 검증하기로 했다. 그래서 기존에 회원 가입 양식을 확인하는 절차를 만들었다.
async function submitJoinForm(e) {
e.preventDefault();
const data = makeDictionary(); // 객체를 만드는 함수
const checker = checkJoinData(data); // 객체를 사용해 데이터를 확인하는 함수 최종적으로 true와 false를 넘겨준다.
if (checker) {
postDataToServer(data); // checker에서 true가 넘어오면 fetch로 데이터를 서버로 보낸다.
}
}
저번에 구상했던 것을 전부 뒤집어 엎었지만 지금이 코드 가독성이나 작동 측면이나 모든 면에서 훨씬 좋은 것 같다. 괜히 sessionStorage를 사용하지 않아도 된다.
일단 document에서 input값을 불러오는 것을 내가 반복하기가 너무 싫었다.(나는 게으르니까...) 그래서 querySelectAll을 사용해서 input을 전부 불러오기로 했다. 그럼 유사 배열로 input을 전부 불러올 수 있다.
function makeDictionary() {
const inputDict = {};// 객체 생각해보니 이렇게 안해도 될것같다.
inputs.forEach((value) => {
if (!inputDict[value.name]) {
inputDict[value.name] = value.value.trim();
}
});
return inputDict; // 그냥 이 값을 {}이렇게 넘겨줘도 될 것 같다.
}
만든 객체를 checkJoinData에 넘겨주고나면 객체 값을 검증하는 함수를 짰다. 최대한 반복을 피하고 싶었다. 그런데 왜 함수가 결국 거기서 거기인 것 같을까. 어쨌든 객체에 저장된 키 값을 각각 email, name, username, password를 검증하는 함수로 보낸 다음에 원하는 값으로 입력되도록 체크하게끔 했다. 아직은 가입자가 뭘 잘못했는지 메시지를 띄워주지는 않는다.
checker 함수는 넘겨받은 문자를 정규 표현식으로 검증한다. 정규 표현식은 구글에 많이 나와있고, 특수문자를 제거하는 방법이나 한글만 입력할 수 있도록 하게 하는 등의 조합법이 친절하게 설명되어있다.
참고한 동영상 : JavaScript Client-side Form Validation
function checkJoinData(obj) {
for (let item in obj) {
switch (item) {
case obj[item] === "":
errorMessage(item);
case "email":
if (!isEmail(obj["email"])) {
errorMessage("email");
return false;
} else {
successMessage("email");
}
case "userName":
if (!isUserName(obj["userName"])) {
errorMessage("userName");
return false;
} else {
successMessage("userName");
}
case "name":
if (!isName(obj["name"])) {
errorMessage("name");
return false;
} else {
successMessage("name");
}
case "password":
if (
isPassword(obj["password"]) &&
obj["password"] === obj["password2"]
) {
successMessage("password");
successMessage("password2");
} else {
errorMessage("password");
errorMessage("password2");
return false;
}
}
}
return true;
}
function errorMessage(str) {
inputs.forEach((value) => {
if (value.name === str) {
value.classList.add("errorWarning");
value.classList.remove("sucessMessage");
}
});
}
function successMessage(str) {
inputs.forEach((value) => {
if (value.name === str) {
value.classList.add("sucessMessage");
value.classList.remove("errorWarning");
}
});
}
function isEmail(email) {
return /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/.exec(
email
);
}
function isName(name) {
return /[ㄱ-ㅎ|ㅏ-ㅣ|가-힣]{2,6}$/.exec(name);
}
function isUserName(userName) {
const checkerUserName = /^[a-zA-Z0-9]{5,10}$/.exec(userName);
if (checkerUserName) {
return checkerUserName;
}
}
function isPassword(password) {
const checkPassword =
/^(?=.*[A-Za-z])(?=.*\d)(?=.*[$@$!%*#?&])[A-Za-z\d$@$!%*#?&]{8,}$/.exec(
password
);
if (checkPassword) {
return checkPassword;
}
}
이렇게 하고나면 일단 프론트에서 검증을 마칠 수 있다. 검증을 할 때, 하나의 값이라도 false가 나오면 false값을 return 하도록 만들었다. 그래서 validation이 처음에 설계한 것과 다르다. 값을 차례대로 return 하기 때문에 함수가 실행 되다 중단된다. 아마도 지금은 개선되지 않았지만 배열에 boolean값을 push하여서 모두가 참이면 true를 리턴하게 하고 하나라도 거짓이면 false를 반환하게끔 하면 값은 값대로 전달되고 양식 입력이 올바르지 않은 곳을 한번에 체크 할 수 있을 것이다.
문제는 회원 가입 양식에 file을 받아야하는데 controller가 file을 계속 인지하지 못했다. 이것으로 거의 3시간을 날렸다.
구글에 열심히 검색을 했다.
- multer with fetch
- req.file undefined multer, fetch, javascript
하지만 답을 쉽게 알수 없었다. status는 400이나 500을 받았다.
async function postDataToServer(data) {
const response = await fetch("/join", {
method: "POST",
headers:{
"Content-Type":"multipart/form-data"
},
body: JSON.stringify(data)
});
console.log(response);
if (response.ok) {
window.location.pathname = "/login";
} else {
console.log("wtf");
}
}
response값이 ok가 아니면 받게되는 'wtf'을 몇 시간동안 받다보니까 나한테 욕하는 것 같아서 기분이 점점 좋아지지 않았다. 처음엔 이것만 해결하고 밥먹어야지 했는데... 하다보니 저녁시간도 놓치고 정말 짜증이 이만 저만이 아니었다.
처음에 multer를 구성하고 있는 middleware가 잘못됐다고 생각했다. 왜냐하면 file을 post하려고 요청을 보냈는데 status값이 500을 받았기 때문이다.
그런데 조금만 생각해보면 근거가 없는 결론이었다. 보내는 값이 잘못된건지 서버가 값을 처리를 못하는건지 알 수가 없잖아? 아마도 처음에 fetch를 할 때, file을 받기 위해 보낸 header값이 잘못 된거라고 생각한다.
headers:{
"Content-Type":"multipart/form-data" //
}
어쨌든 이 값을 "application/json"으로 바꿔주니까 file은 여전히 undefined이지만 나머지 body값은 json으로 넘겨 받을 수 있었기 때문이다.
결론적으로
해결하는데 도움이 된 stackoverflow 게시물
req.file is undefined using multer
내가 만든 객체 값을 json으로 서버로 보내는 방법이 잘못되었다.
async function postDataToServer(data) {
const file = new FormData(form); // 아얘 form데이터를 받아서 fetch data로 넘겨주었더니 해결되었다.
const response = await fetch("/join", {
method: "POST",
body: file
});
console.log(response);
if (response.ok) {
window.location.pathname = "/login";
} else {
console.log("wtf");
}
}
이 요청에 적당한 Http header값을 지금은 모르겠다. 그런데 header를 제거하고 body에 FormData로 생성한 form값을 한번에 넘겨주었더니 컨트롤러에서 body값뿐 아니라 file까지 전부 인식했다.
지금까지의 과정으로 추측해볼 수 있는 것은 post를 요청할 때, router가 postJoin을 하기 전에 multer를 거치도록 되어있는데 이때 데이터를 json이 아니라 form 데이터를 한번에 넘겨주면 multer가 data를 보고 원래대로 파일과 body값을 전부 넘겨주는 듯 하다.
그럼 front의 validation이 헛수고가 아닌가 생각할 수 있지만 그렇지도 않다. fetch로 넘어가기전에 값을 검증한 다음에 넘겨주는 거니까 의미가 없지 않다.
로컬 서버에서 성공은 했다. 문제는 heroku 업데이트 후에 회원가입이 정상적으로 작동을 하지 않는다. 이유가 뭘까? 잘 모르겠다. 일단 status code는 500을 받는다. server에서 뭔가 문제가 생긴것이 분명한데 AWS S3로 파일을 넘겨주면서 아마도 뭔가 문제가 있는게 아닐까? 그냥 추측을 해볼 뿐이다.
그럼 정상적으로 작동을 시키기 위해서 조금 더 코드를 정밀하게 만들어야한다.
회원 가입이 되지 않는 이유를 찾기 위해서 해야 할 것들
- controller에서 unique 검증이 실패했을 때, status code를 front로 보내주는데 이렇게 했을 경우에 front에서 error메시지를 어느 부분에서 실패한 것인지 보여주어야한다. 지금 코드는 UX 디자이너가 봤을 때, WTF이다.
- front에서 검증이 실패했다면 그냥 빨간색 줄만 긋지 말고 왜 실패했는지 메시지를 출력해야한다.
- S3로 파일 업데이트를 실패하기 때문에 회원 가입이 되지 않는 것인지 아닌지를 알기 위해서는 일단 파일을 업로드 하지 않고 회원 가입을 진행 해본다. 다른 조건이 성공 조건인데 여전히 회원 가입에 실패한다면 S3를 의심해보기도 전에 fetch에서 문제가 있다고 판단해볼 수 밖에 없다.
계속 테스트를 반복하다보니까 만만치가 않다. 아마 디버깅 방법이 그닥 좋지 않기 때문이 아닐까? console.log로 값을 찍어서 보는 것이 한계가 있다. 그런데 nodejs는 디버깅 어떻게 하는거냐? 🥲