🐈오늘 배운 것
✔️Next.js API 함수에서 async await 사용
Next.js 에서 API 내부의 로직을 수행하는 함수는 프로미스를 반환하도록 해야한다. Next.js API 함수를 만들 때 이를 async await 으로 만들지 않으면 어떻게 되는지 궁금해서 직접 해봤다.
export default function withHandler({method, handler}) {
// 리턴되는 함수가 withHandler 함수 호출시 실행될 함수 : 이 부분이 async await 으로 작성되어야 함.
return function (req, res) {
handler(req, res);
//... 생략
};
}
위와 같이 작성하니, 동작은 하는데 터미널에 아래와 같은 문구가 생겼다.
API resolved without sending a response for /api/users/login, this may result in stalled requests.
(대충.. response를 보내지 않고 API가 처리되었는데, 이러면 요청이 지연될 수도 있다는 말인듯하다.)
API 에서 함수들끼리 연속적으로 무언가를 처리하려면 하나의 API 에서 작업이 수행되고 있음을 감지할 수 있도록 프로미스를 반환하고 콜백이 실행될 때까지 기다리려야 한다는 설명이 있다.
위 함수에서 이런 메시지가 안뜨게 하려면 아래와 같이 작성하면 된다.
export default function withHandler({method, handler}) {
return async function (req, res) {
await handler(req, res);
//... 생략
};
}
async await, promise 도 그렇고 api 에서 함수를 작성할 때 export default 로 작성하는 것도 지금은 그냥 그렇게 쓰는거구나 라고 이해하고 있는데, 왜 그런지 조금 더 공부해봐야겠다.
✔️인증번호 이메일 발송 & UI 조건부 렌더링, 커스텀 훅 만들기
캐럿마켓 클론 코딩 수업을 들으면서 배웠던 부분을 다시 복습하며 만들어보고 있다. 오늘 작업한 부분은
- 사용자 이메일을 토대로 유저 정보 및 인증번호 생성 및 DB 저장
- 이메일로 인증번호 발송
- 이메일 입력 후 인증번호 입력 UI로 조건부렌더링
- fetch를 위한 커스텀 훅 만들기
수업을 들을때는 후루루룩 넘어갔던 부분같은데 역시 다시 보니 어렵다...!💦
글이 너무 길어서 코드는 더보기 글 안에 정리했다.. ^^
먼저 클라이언트에서 POST 을 하는 로직이다. 처음에는 컴포넌트 안에 작성했다가 커스텀 훅으로 바꿔서 적용했다.
// POST 요청, 요청 상태 변경
export function usePost(url) {
const [state, setState] = useState({
loding: false,
response: undefined,
error: undefined,
});
// POST 요청 처리 로직
function fetchPost(payload) {
setState(prev => ({...prev, loading: true}));
fetch(url, {
method: 'POST',
body: JSON.stringify(payload),
headers: {
'Content-Type': 'application/json',
},
})
.then(response => response.json())
.then(response => {
setState(prev => ({...prev, response}));
})
.catch(error => setState(prev => ({...prev, error})))
.finally(() => {
setState(prev => ({...prev, loading: false}));
});
}
return [fetchPost, state];
}
// 커스텀 훅을 사용할 컴포넌트 안에서...
const [login, {loading, response, error}] = usePost('/api/users/login');
// 위와 같이 분해해서 사용. login에 payload를 넣어 호출하면 fetch가 되면서
// 리턴되는 state들도 업데이트가 된다. submit을 한다면 해당 이벤트가 일어나는 함수 안에다가
// login을 호출해서 변경되도록 트리거 할 수 있음
fetchPost 라는 함수와 fetchPost 가 호출된 뒤에 업데이트가 적용되는 state 객체를 리턴한다. 처음에는 각 메서드별로 fetch를 분기해서 처리할까 고민했는데, 그러자니 하나의 함수가 너무 길어져서 그냥 메서드별로 별개의 함수를 만드는게 맞는 것 같다. 함수 이름도 어떻게 지어야할지 고민을 많이 했는데 다시 봐도 막 매끄럽진 않은 것 같다.
커스텀 훅을 import 한 코드에서 fetchPost 함수와 각 state를 불러오고, fetchPost 에 POST 요청으로 보내줄 payload 를 전달하면 성공적으로 요청과 응답이 완료되어 loading, data 만약 error 가 있다면 error state에 각각 업데이트가 된다.
블로그를 쓰다보니 문득 신기하게 느껴진 점... 커스텀 훅은 컴포넌트간에 props로 전달하지 않아도 state에 접근하고 재사용 할 수 있는 것 같다...! 생각해보니 별도의 props 전달 과정을 거치거나 특정 컴포넌트에서만 쓸 수 있는게 아니라 커스텀 훅으로 불러오기만 하면 어디서든 쓸 수 있는 것 같다. 잘 활용하면 전역에서 관리되는 상태로 활용할 수도 있지 않을까싶다.
그리고 API 에서 유저가 입력한 이메일을 받아서 DB에 생성하고, 인증번호를 이메일로 발송하는 로직
async function handler(req, res) {
const user_data = req.body;
const token = await prisma.token.create({
data: {
payload: getRandomNumber() + '',
user: {
connectOrCreate: {
where: {
...user_data,
},
create: {
name: '익명 사용자',
...user_data,
},
},
},
},
});
// 보낼 email, title, contents 설정
const mailOption = mailOpt(
user_data.email,
'포도 마켓 인증메일',
`당신의 포도 마켓 인증번호는 : ${token.payload} 입니다.`,
);
// mail 발송
sendMail(mailOption);
res.status(200).json({result: true});
}
먼저 hander 함수는 method가 올바른지, 에러가 발생하지는 않는지를 검증하고 함수를 실행시켜주는 withHandler 라는 커스텀 핸들러를 사용해서 호출하려고 한다.
promise.token.create 부분은 token 모델에 새롭게 레코드를 생성한다는 prisma client로 사용하는 쿼리문이다. 공식문서대로 async 함수 안에서 await 을 붙여서 호출한다. 먼저 토큰의 payload는 랜덤으로 생성된 인증 번호이고, user 필드에 접근해 유저가 입력한 이메일과 일치하는 것이 있으면 토큰과 연결하고, 만약 일치하는 데이터가 없으면 익명 사용자라는 이름으로 새롭게 생성한다.
아래 이메일 발송 부분은 nodemailer 적용하기라는 어떤 분의 포스팅을 참고해 별도의 함수를 정의해서 해결했다.
이 함수의 실행이 정상적으로 되면 메일이 발송되고, 클라이언트로 전송되는 result 값의 여부에 따라 UI가 조건부 렌더링 되도록 했다.
일단 오늘도 여러방면으로 다양한 삽질을 했는데, 그 중 주된 삽질은 바보같은 실수때문이었다. 벌써 두번째로 객체 형태로 넘어오는 인자를 분해하지 않아서 어이없게 헤맸다. 받아오는 type이 달라 에러가 나는 것 같아 확인해보니 string 으로 들어와야 할 값이 Object 로 들어오고 있었고, 같이 들어오는 함수도 마찬가지였다 ㅜㅜ. 이런 자잘한 실수때문에 집중도도 떨어지고, 어디가 원인인지 금방 파악을 못하다보니 디버깅을 하는데 쓸데없이 시간을 쓰는 것 같다. 타입스크립트를 빨리 배워야겠다고 생각했다. 적어도 어디가 문제인지는 알아볼 수 있게 ㅜㅜ.
✔️GraphQL 사용해보기(조회)
GraphQL 줄여서 gql 은 클라이언트 - 서버 API 를 위한 쿼리 언어로, 서버의 데이터들을 직,간접적으로 연결된 그래프 구조로 본다. 그래프 구조로 바라보면 여러 데이터간의 연결 관계를 표현할 수 있고, 특정 기준에 따라 계층적으로 구성된 트리 구조로 표현될 수 있다. 클라이언트의 요청에 따라서 이 트리구조를 JSON 으로 전송해줄 수 있다.
REST API 방식의 단점을 해소할 수 있는데, REST API 에서 리소스의 응답이 고정된 형태로 정해져있다면 클라이언트 쪽에서 변경이 생겼을 때 API 서버도 변경이 필요할 수 있다. GQL 에서는 리소스를 정의하는 것과 데이터를 요청하는 것이 별개이고, 리소스가 정의되어 있을 때 클라이언트에 그 중 필요한 부분만 요청할 수 있다.
따라서 필요없는 데이터까지 같이 응답을 받는 overfetching 이나, 반대로 필요한 데이터가 빠진 응답을 받는 underfetching을 해소할 수 있다.
여러 리소스에 접근하기 위해서 REST API 에서는 각 엔드포인트별로 요청을 여러번 해야하는데, gql 에서는 하나의 엔드포인트를 통해 한 번에 여러 리소스를 요청할 수 있다는 장점이 있다. playground 라는 GUI 를 제공해주고 있어서 resolver 와 shcema 를 POSTMAN 처럼 한 눈에 보고 테스트도 할 수 있다.
단점으로는 메소드별로 캐싱이 지원되는 REST API 와 달리 gql 에서는 조금 더 복잡하다. (REST API 에서는 캐싱을 기본적으로 브라우저가 지원한다고 한다. 참조) gql 에서는 HTTP 의 POST 메서드만을 사용하기 때문에 캐싱을 지원받을 수 없고 이를 보완하기 위해 위해 Apollo 엔진의 캐싱, 영속 쿼리등을 사용한다.
REST API 의 메서드와 비슷한 Query, Mutation 키워드로 서버에 쿼리한다. 또 데이터에 변동이 생겼을 경우 실시간으로 클라이언트로 이를 응답해주는 Subscription 개념이 있다. 이들 3가지 키워드를 Operation Type 이라고 하며, 클라이언트에서 해당 작업에 맞게 쿼리를 할 때 이 키워드와 오퍼레이션 이름을 붙인다.
백엔드에서 스키마를 정의하면 프론트에서 정의된 스키마를 토대로 객체와 필드를 조회할 수 있다. 스키마는 프론트와 백엔드가 소통하기 위한 협약같은 것이고, 프론트에서는 이 정의된 협약을 토대로 원하는 데이터를 쏙쏙 골라서 요청할 수 있는 것이다. 스키마는 gql 타입의 모음으로 이루어져 있는데, 요청을 통해 전달받을 각 객체의 필드와 타입에 대한 정의가 필요하다.
Query, Mutation 같은 루트 타입(루트 필드)에 대해서도 정의가 필요한데, 공식문서에 의하면 Query 타입에 대한 정의는 필수이고 Mutation 은 있을 수도 있고, 없을 수도 있다고 한다. CRUD 에 해당하는 요청을 받아들이려면 이들 타입에 대해서 추가적인 필드 정의가 필요하다.
query 라는 키워드를 사용하면 아래 필드에 대해 조회하겠다는 의미이다. 반대로 mutation 일 경우 수정, 생성, 업데이트의 의미이다.
클라이언트에서 쿼리를 날리는 방법은 간단하게는 아래와 같다.
query HeroNameAndFriends($episode: Episode) {
hero(episode: $episode) {
name
friends {
name
}
}
}
동적으로 변하는 값을 인자로 전달할 수도 있는데, 먼저 query 키워드 옆의 오퍼레이션 이름 옆의 괄호 ($episode: Episode) 변수 episode 와 Episode 라는 타입을 의미한다. hero 객체에서 episode 필드가 동적으로 변하는 값인 $episode 인 경우 그 데이터의 name 과 friends 라는 필드를 조회하라는 쿼리문이다.
🐈더 공부할 것
1. iron session 적용, 유저 인증 및 권한부여
2. 기초 CS 복습
3. graphQL 더 연습해보기
4. 비동기 처리
🐈오늘의 느낀 점
배우면 배울수록 모르는게 여기저기서 튀어나오는 느낌이다. graphQL 이라는 새로운 언어를 알게 되었고 빨리 배워서 활용해보고 싶다. 하지만 시간이라는게 한정되어있기 때문에 하던 것을 내버려두고 또 새로운 것을 하는건 더 비효율적인 것 같다. 하나의 언어를 숙달하면 다른 새로운 언어를 배우는게 정말 빨라진다고 하는데... 오늘 graphQL 에서 스키마를 만드는 부분이 전에 prisma 로 스키마 모델을 만들었던 부분과 유사해서 쬐끔 이해하는데 도움이 된 것 같기는 하다.
자바스크립트를 공부하면서 타입에 대해서는 거의 신경을 쓰지 않았고 그래서 거기에 익숙해져 있는 것 같다. 오늘 타입때문에 쓸데없이 시간을 허비하기도 했고 또 gql 스키마에서도 계속 타입을 지정하는 것에 대한 이야기가 나와서 타입에 더 익숙해있다면 좋겠다는 생각을 했다. 역시 답은 타입스크립트...!
'TIL' 카테고리의 다른 글
[TIL] 2022-1011 (0) | 2022.10.12 |
---|---|
[TIL] 2022-1010 (0) | 2022.10.10 |
[TIL] 2022-1003 (0) | 2022.10.03 |
[Day 73] 2022-0928 (1) | 2022.09.29 |
[Day 72] 2022-0927 (0) | 2022.09.28 |