이런저런 바빴다는 이유(핑계)로 꽤나 오랫동안 멀리했던 graphql을 리마인드 해보고 싶어서 다뤄보게 되었다. 이번에 구현할 미니 프로젝트의
- 로그인
- 로그아웃
- 게시물 등록
- 댓글 작성
- 게시물 좋아요
그리고?
- FE : React + TypeScript
- BE : GraphQL
일단 apollo-server를 이용하여 서버를 구현해보자.
const server = new ApolloServer({
typeDefs,
resolvers,
context: ({ req }) => ({ req }),
})
context는 req요청을 받을 수 있게 설정해준다.
(ex. req.headers)
typeDefs.js 파일에는 역시 apollo-server의 gql을 사용하여
input RegisterInput {
username: String!
password: String!
confirmPassword: String!
email: String!
}
type User {
id: ID!
email: String!
token: String!
username: String!
createdAt: String!
}
요러케 유저 타입도 정의하고
type Mutation {
register(registerInput: RegisterInput): User!
login(username: String!, password: String!): User!
}
REST API를 대체하기 위한 mutation도 정의해준다
(게시물 관련 Query 및 Mutation은 생략)
type을 사용하여 정의하는 것이 가장 깔끔한 것 같지만 굳이 input을 사용한 이유는 두 타입이 서로 다르게 동작한다는 것을 알려주기 위함이라고 하는 것 같다 (맞나?)
본격 typeDefs를 선언된 타입을 정의해주기 위한 Mutation을 세세하게 정의해주기 위한 resolver를 정의해주도록 하자
먼저 회원가입은 bcrypt및 jwt를 사용하여 진행하였다.
Mutation: {
register: async(
_,
{ registerInput: { username, email, password, confirmPassword } }
) => {
password = await bcrypt.hash(password, 12)
const token = jwt.sign(
{
id: user.id,
email: user.email,
username: user.username,
},
SECRET_KEY,
{ expiresIn: "1h" }
)
}
여기서 mutation()안에 들어가는 변수는 총 4가지이다.
register(parent, args, context, info)
- parent: 부모 resolver에서 반환되는 객체이다
- args: Query에서 정의한 변수가 들어가는 곳이다
- context: 앞서 context를 정의했다면 이 부분에서 request 요청을 처리할 수 있게 된다.
- info : 아직 사용해 본 적이 없음...
기존 node 미들웨어와 같이 진행하였지만, 앞에서 언급했던 context를 사용할 수 있는 시간이 왔다.
module.exports = (context) => {
const authHeader = context.req.headers.authorization
if (authHeader) {
const token = authHeader.split("Bearer ")[1]
if (token) {
try {
const user = jwt.verify(token, SECRET_KEY)
return user
} catch (error) {
throw new AuthenticationError("Invalid/Expired token")
}
}
throw new Error("token형식이 올바르지 않습니다.")
}
throw new Error("header가 존재하지 않습니다.")
}
차이점은 바로 헤더 및 토큰을 context.req.headers를 통해 받아온다는 것이다. 그 부분을 제외하면 사실상 node에서 구현했던 코드와 동일...? 하다...!
(물론 서버 부분에서 context: ({ req }) => ({ req }) 를 작성해줘야 받아올 수 있다!)
token을 생성할 때 id, email, username 이 세 가지를 이용하였으므로, token이 있을 경우 이 세가지를 포함한 객체를 리턴해 줄 것이다. 그리고 미들웨어를 적용시켜 줄 때는
createPost: async (_, { body }, context) => {
...
const user = authMiddleware(context)
...
}
위 코드와 같이 context를 정의한 뒤 사용해 주면 끝!
참 쉽죠?
서버에서 graphql을 정의하는 것은 구조만 익숙하다고 했다면 사실 기존 node를 사용할 때와 다른 것은 없었다. 이 요청들을 client에서 불러오거나 받아올 때 graphql의 진가가 들어나는 것 같다!
React를 이용한 client-side는 다음 편에...