Apollo로 graphql 서버와 resolver 만들기
dooboolab 님께서 작성한 Creating graphql server and first resolver with Apollo 번역입니다. 피드백 환영합니다!
이번 포스팅에서는 apollo-server 와 함께 어떻게 첫 graphql 서버를 구현하는지 알아보려고 합니다.
서버를 구현하기 위해 선택한 기술 스택은 다음과 같습니다.
graphql 을 구현이 처음이라면, 더 깊은 개념을 이해하기 위해 서버 개발에 대한 개념을 먼저 숙지하고 오는 것을 추천합니다.
apollo-server-express 는 express framework 위에 보다 더 쉽게 graphql resolver 를 구현할 수 있는 기능을 제공합니다. 이것은 express 를 얼마든지 사용하면서도 그 상단에 graphql resolver 를 구현할 수 있다는 것을 의미합니다. 어떻게 이것이 가능한지 함께 따라가면서 보겠습니다.
이미 위에서 명시했듯, 데이터베이스 ORM으로 sequelize
를 사용합니다. Prisma 를 다뤄본 적이 있다면, sequelize
는 Prisma2 에서 photon 과 유사하다는 것을 알 수 있습니다. sequelize
또한 비슷한 기능들을 제공하고 있기 때문에 우리 graphql 서버에서 쉽게 대체할 수 있었습니다.
첫단계로 서버를 제작하기 전에 데이터베이스 스키마부터 먼저 살펴보는 것을 권장합니다.
graphql 은 SDL(Schema Definition Language) 가 주(主)이지만, 저는 SDL 가 구조화가 잘 이루어진 관계형 데이터베이스에 영향을 받는다고 생각합니다. 내부적으로는 결국 이 관계형 데이터베이스가 작동하고 있기 때문입니다.
1. 데이터베이스 모델 생성하기
위 그림 4, 5 처럼 RDBMS 를 설계 했습니다. 그 후 sequelize
ORM으로 설정합니다.
getting started guide from sequelize 를 참고하여 데이터베이스 모델을 생성하고 실제 데이터베이스와 연동하겠습니다.
우리 프로젝트는 타입스크립트를 사용하기 때문에, User
모델을 나타내는 Model
파일이 꽤 길어졌습니다. 현재 sequelize 의 타입스크립트 지원은 보는 바와 같이 그리 좋진 않습니다.
그럼 Notification
모델과 Post
모델을 생성하겠습니다.
위 과정을 전부 마쳤다면, ./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/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,
};
(Prisma2의 Lift와 비슷한) Sequelize migration 을 통해 실제 데이터베이스와 모델들을 연동할 수 있도록 config
파일은 순수 자바스크립트 파일(.js)로 작성합니다. 혹시나 config.js
을 config.ts
로 변경한다면, sequelize migration 커맨드가 마이그레이션 작업 실행을 위한 파일을 찾지 못합니다.
sequelize 는 주요 사용하는 다섯가지 RDB 인 Postgres 와 MySQL, MariaDB, SQLite, Microsoft SQL 을 지원합니다. 우리는 이 포스팅에서 MySQL 을 사용합니다.
2. Apollo Server 설정하기
각 라인별로 위의 코드를 설명해보겠습니다.
17–33 라인에서는 사용자 인증(user authentication)을 확인하기 위해 JWT 토큰을 확인하는 함수들을 볼 수 있습니다.
35–73 라인에서는, Apollo Server를 초기화합니다.
이중 38–41 라인에서 context
를 이용하여 커스텀 미들웨어를 전달합니다.
getUser: () => Promise<User>;
models: ModelType;
pubsub: PubSub;
appSecret: string;
getUser
는 사용자가 로그인 했을 때 resolver 에서 인증하기 위해 사용합니다.models
은Sequelize
에서 가져온 것으로 실제 데이터베이스의 CRUD를 수행합니다.pubsub
은 websocket 을 사용하는 graphql-subscription 을 위한 것입니다.appSecret
은 자신의JWT
secret 입니다.
74 라인부터는, apollo server 를 subscriptions 과 함께 초기화하고 실행합니다.
3. graphql SDL로 스키마 만들기
./schemas
에 스키마를 정의하겠습니다.
이 schema
들의 루트 파일은 schema.graphql
입니다. 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 모델들을 SDL 로 정의 내려야합니다. 네, 같은 작업을 불만스럽게도 두 번 수행하는 것이지요.
Primsa 를 사용하고 있다면 번거롭게 두 번 같은 작업을 하지 않아도 괜찮습니다. 하지만 Prisma2 가 안정적인 버전으로 나올 때까지는, 보수적인 방법으로 작업을 할 수 밖에 없습니다. 이런 접근은 Prisma 같은 graphql ORM 들을 이해할 수 있게 해줍니다.
그림10
을 좀 더 자세하게 보면, model
관계가 배열로 추가된 것을 볼 수 있습니다.
graphql 은 관계한 모델들을 위 그림11
처럼 fetch 하기 때문입니다. 조금 뒤에 resolver 에서 저 logic 을 만들어보겠습니다.
4. codegen 설정 및 실행
모든 스키마를 정의 했다면, SDL을 현재 사용 중인 프로그래밍 언어로 번역하기 위한 코드를 만들어야 합니다. 우리는 타입스크립트를 사용 중이기 때문에 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
은 context 를 위한 타입 정의입니다. 이 context는 server.ts 의 context
로 넘겨줍니다. 이 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;
}
5. resolvers 구현
signUp
mutation resolver 를 구현하겠습니다.
성공적으로 우리의 첫 resolver 를 만들 수 있었습니다 🎉
이 포스팅 이후에는 어떻게 다수의 resolver 와 query 관계를 효율적으로 추가할 수 잇는지 알아보겠습니다.
이 포스트 시리즈는 우리의 보일러플레이트 프로젝트인 ts-apollo-sequelize 에서 가져왔습니다.