[번역] 아폴로를 이용한 그래프 큐엘 서버 구현
해당 글은 두부랩(dooboolab)과 그래프 큐엘-서울에서 쓰여진 Creating graphql server and first resolver with Apollo 을 번역한 글입니다.
이 글은 Apollo-Server를 이용해 그래프 큐엘 서버를 구현하기 위한 프로젝트 글입니다.
GraphQL 구현을 위한 서버의 기술 스택은 다음과 같습니다.
- Node.js : V8 엔진 기반의 자바스크립트 런타임.
- Express : Node.js 기반의 서버 구축 프레임워크.
- Typescript : Javascript에 타입을 제공하는 컴파일 언어.
- Sequelize : ORM으로 데이터 베이스 조작을 쉽게 해주는 라이브러리.
- Jest : 테스트를 위한 라이브러리.
그래프 큐엘 서버를 처음 구현한다면, 최대한 서버 프로그래밍에 대한 지식을 먼저 익혀야 합니다. Graph QL를 깊게 이해하기 위해서는 다양한 서버 프로그래밍 지식이 필요하기 때문입니다.
(개인적 생각) REST API, Over/Under Fetching, HTTPS, 스키마, 서버, 데이터 베이스에 대한 학습이 기본적으로 선행되어야 합니다.
Express 프레임워크 위에서그래프 큐엘 리졸버(Resolver)를 쉽게 사용하기 위해 apollo-server-express 라이브러리를 쓸 수 있습니다. 이러한 방법을 통해 express 자체에서 제공하는 기능을 함께 사용할 수 있습니다.
처음 기술 스택에서 언급한 것 처럼, 해당 프로젝트에서는 데이터 베이스 ORM(Object-Relational-Mapping)으로 sequelize 라이브러리를 사용합니다. 만약, Prisma(프리즈마)를 이미 써보셨다면, Prisma2(프리즈마2)에서 소개하는 photon과 비슷한 역할을 하는 라이브러리 입니다.
Sequelize는 photon과 비슷한 기능을 제공하기 때문에, 해당 프로젝트에서는 sequelize를 사용하여 데이터베이스를 조작할 것입니다.
본격적으로 GraphQL서버 구현에 앞서, 데이터 베이스 스키마(모델링)를 정하는 것이 좋습니다. 그래프 큐엘에서 데이터를 정의하기 위해 SDL에 초점을 두지만, SDL에 정의한 리졸버를 구현할 때 잘 설계된 데이터 베이스 모델이 도움이 되기 때문입니다.
01. 데이터베이스 모델 생성
해당 프로젝트를 위해 위와같이 RDB를 작성하고, 이를 통해 Sequelize
orm 환경 설정을 해야합니다. sequelize 공식 문서의 getting started guide를 따라하며 데이터 베이스 모델을 생성하고 실제 데이터베이스와 연동할 수 있습니다.
시퀄라이즈에 타입스크립트(Typescript)를 도입하려다 보니 User 테이블을 정의하는 파일이 다소 길어졌습니다. 현재로서 시퀄라이즈의 타입스크립트 지원이 조금 까다로워, 불가피하게 코드가 길어졌습니다.
User 모델 외에도 Notification
과 Post
모델을 다음과 같이 정의합니다.
이후 실제 데이터베이스와 ORM의 연동을 위한 ./db/index.ts
파일을 생성해주어야 합니다.
import { Sequelize } from 'sequelize';
import config from '../../config/config';const sequelize = new Sequelize(
config.database,
config.username,
config.password,
config,
);if (process.env.NODE_ENV !== 'test') {
sequelize.sync();
}export default sequelize;
데이터 베이스 설정 정보는 아래와 같이 ./config/config.js
파일에 작성합니다.
const config = {
username: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: process.env.DB_DATABASE,
host: process.env.DB_HOST,
port: process.env.DB_PORT,
dialect: process.env.DB_CONNECTOR,
};
이때, config.js
는 최대한 타입스크립트 파일이 아닌, js 확장자를 가지는 것이 좋습니다. 왜냐하면 Sequelize에서 제공하는 마이그레이션(Migration)기능을 사용할 때, config.ts
를 인식할 수 없기 때문입니다.
Sequelize Migration 기능은 Prisma2의 Lift와 비슷한 기능으로 데이터베이스에 새로운 속성을 추가할 때 기존 데이터베이스의 데이터를 삭제하는 일 없이 추가해주는 기능입니다.
Sequelize는 Postgres, MySQL, MariaDB, SQLite, Microsoft SQL, MySQL등 주로 사용되는 관계형 데이터 베이스(RDB)를 지원하며 해당 프로젝트에서는 MySQL을 사용합니다.
02. 아폴로 서버 설정
코드에 대한 설명을 간략하게 한다면,
17~ 33 번 줄 : 유저 인증을 위한 JWT를 검증하는 코드
35~73 번 줄 : 아폴로 서버를 초기화 하는 코드이며 38번 줄과 41번줄은 커스텀 미들웨어를 context에 추가하는 코드입니다.
getUser: () => Promise<User>;
models: ModelType;
pubsub: PubSub;
appSecret: string;
getUser
유저가 로그인 했는지를 검증하는 미들웨어.models
실제 데이터베이스에 시퀄라이즈를 이용해 CRUD 작업을 수행하기 위한 미들웨어.pubsub
웹 소켓 기반의 graphql-subscription을 위한 미들웨어appSecret
JWT 비밀키
이후 나머지는 Apollo 서버와 graphql-subscription을 함께 Initialize 해주는 코드입니다.
03. Graph QL SDL과 함께 스키마 만들기
Graph QL을 이용하기 위해 ./schemas
디렉토리에 스키마를 정의해야 합니다.
스키마 파일의 root 파일(진입점)은 schema.graphql
입니다. 이 부분에 Query
와Mutation
,Subscription
에 대한 리졸버(Resolver)를 정의합니다.
# import User from "user.graphql"
# import Notification from "notification.graphql"
# import Post from "post.graphql"type Query {
users: [User!]!
user(id: ID!): User
posts: [Post!]!
notifications: [Notification!]!
}type Mutation {
signInGoogle(socialUser: SocialUserInput!): AuthPayload!
signInFacebook(socialUser: SocialUserInput!): AuthPayload!
signInApple(socialUser: SocialUserInput!): AuthPayload!
signUp(user: UserInput!): AuthPayload!
addNotificationToken(notification: NotificationInput!): Notification
updateProfile(user: UserInput!): User
}type Subscription {
userAdded: User
userUpdated: User
}
이후에 Sequelize와 Graph QL 서버를 구현할 때 귀찮은 SDL을 작성해야 합니다. (이 부분은 모델에 대한 정의를 2번씩 써주어야 해 귀찮은 부분입니다)
만약 Prisma를 사용하고 있다면, 위와 같이 두 번씩 모델을 정의할 필요가 없지만, Prisma가 아직 과도기적 단계에 있기에 좀 더 안정적인(전통적인) 방법을 사용할 것입니다.
아마도 Prisma또한 내부적으로 이와 비슷하게 구현될 것이기에, 이를 이해한다면 Prisma와 같은 Graph QL ORM을 이해하는데 이런 접근법이 도움이 될 것이라 생각합니다.
[그림 10]을 좀더 자세히 보면, GraphQL 스키마(오른쪽) 부분에 Notification 모델이 배열과 같이 [Notification]으로 추가된 것을 확인할 수 있습니다.
이는 [그림 11] 에서 보는 것과 같이 Graph QL에서 관계형 모델(1:N, N:M)을 위와 같이 Fetching 하기 때문입니다.
04. Codegen 설정 및 실행
모든 스키마를 정의한 후에, SDL을 현재 사용하는 프로그래밍 언어로 변환해주는 코드를 생성해야합니다. 현재 프로젝트에서는 Typescript를 사용하고 있기에 Typescript 플러그인을 사용할 것입니다.
codegen.yml
파일을 아래와 같이 작성하고, graphql-codegen을 통해 실행시켜봅시다.
overwrite: true
schema: './schemas/schema.graphql'
documents: null
generates:
src/generated/graphql.ts:
config:
contextType: ../context #MyContext
plugins:
- 'typescript'
- 'typescript-resolvers'
codegen.yml
에 구현된 contextType
는 server.ts에 있는 context
에 대한 타입 정의 입니다. ./src/context
에 아래와 같이 작성합니다.
import { ModelType } from './models';
import { PubSub } from 'graphql-subscriptions';
import { User } from './models/User';export interface MyContext {
getUser: () => Promise<User>;
models: ModelType;
pubsub: PubSub;
appSecret: string;
}
05. 리졸버(Resolver) 구현
signUp
mutation에 대한 리졸버(Resolver)를 구현해 보면 아래와 같습니다.
드디어, 첫 번째 리졸버를 완성했습니다.
해당 포스트는 앞으로 어떻게 다양한 리졸버를 추가할 것인지와 어떻게 쿼리를 효과적으로 구성할 것인지에 대한 내용을 꾸준히 업로드 할 것입니다.
해당 포스트 시리즈에 대한 보일러플레이트는 ts-apollo-sequelize 프로젝트를 참고했습니다.