Hello, GraphQL

Part 2
Contents
- GraphQL 구현 (Code ? SDL ?)
- SDL First 프로젝트 구조
- Resolver 이해
- 간단한 API 작성
GraphQL 개발 과정

초기 Facebook 에서 release 한 graphql-js는 Code-First
Apollo Group - SDL-First 개발
Code-First를 지원하는 여러 Library, Framework 가 나오고 있음
SDL(Schema Definition Language)
type User {
id: Int!
name: String!
username: String!
email: String!
phone: String!
posts: [Post!]!
}
type Post {
id: Int!
title: String!
body: String!
user: User!
}
type Query {
users: [User]!
user(id: Int!): User!
posts: [Post]!
}Facebook graphql-js
import {
graphql,
GraphQLSchema,
GraphQLObjectType,
GraphQLString,
} from 'graphql';
var schema = new GraphQLSchema({
query: new GraphQLObjectType({
name: 'RootQueryType',
fields: {
hello: {
type: GraphQLString,
resolve() {
return 'world';
},
},
},
}),
});Schema 구조를 별도로 표현하지 않음
코드상에 Type을 정의하고 구현도 같이 녹아들어감
SDL-First
import { users, posts } from './db';
const resolvers = {
Query: {
users: () => users;
user: ( _, { id }) => users.find(u => u.id === id);
posts: () => posts;
},
Post: {
user: ({ userId }) => users.find(u => u.id === userId)
},
User: {
posts: ({ id }) => posts.filter(p => p.userId === id)
}
};
export default resolvers;Schema 구조를 이해하기 쉬움
설계 프로세스가 보다 직관적으로 이뤄질 수 있음
Code-First Library (TypeGraphQL)
@Service()
@Resolver(() => Issue)
export class IssueResolver {
constructor(private readonly issueService: IssueService) {}
@Authorized()
@Query(() => [Issue])
public async issues(@Args() args: IssueArgs) {
return this.issueService.getIssues(args)
}
@Authorized()
@Query(() => Issue)
public async issue(@Arg('id') id: number) {
return this.issueService.getIssue(id)
}
@Authorized()
@FieldResolver(() => [Event])
public async events(
@Root() issue: Issue,
@Ctx() { loaders: { eventsLoader } }: ApolloContext,
) {
return eventsLoader.load(issue.id)
}
}GraphQL Quick Start
const { ApolloServer, gql } = require('apollo-server');
const typeDefs = gql`
# Comments in GraphQL strings (such as this one) start with the hash (#) symbol.
# This "Book" type defines the queryable fields for every book in our data source.
type Book {
title: String
author: String
}
...
`;
const resolvers = {
Query: {
books: () => books,
},
};
const server = new ApolloServer({ typeDefs, resolvers });
server.listen().then(({ url }) => {
console.log(`🚀 Server ready at ${url}`);
});typeDefs, resolvers 를 정의하고 Apollo Server 에 전달
Folder structure
- Domain 별로 typeDefs, Resolver 필요
- BusinessLogic Layer
- DB Models
- Utility
- Etc...
GraphQL Folder Struture
.
├── package.json
└── src
├── data
│ └── index.js
├── models
│ ├── Book.js
│ └── index.js
├── resolvers
│ ├── booksResolvers.js
│ └── index.js
└── typeDefs
├── index.js
├── query.js
└── types
├── bookType.js
└── index.jsRole 단위
Domain 단위
.
├── package.json
└── src
├── books
│ ├── Book.js
│ ├── data.js
│ ├── index.js
│ ├── resolvers.js
│ └── typeDef.js
└── index.jsgraphql-tools Schema 구성
├── src
│ ├── graphql
│ │ ├── directive
│ │ ├── entity
│ │ │ └── event
│ │ │ ├── event.graphql
│ │ │ └── event.resolver.ts
│ │ ├── index.tsDomain 별로 나뉘어있는 typeDefs, resolvers 로딩 후 merge
entity 아래 domain 별로 graphql, resolvers 구성
graphql-tools Schema 구성
graphql/index.ts
import path from 'path'
import { loadFilesSync } from '@graphql-tools/load-files'
import { mergeTypeDefs, mergeResolvers } from '@graphql-tools/merge'
const typesArray = loadFilesSync(path.join(__dirname, './**/*.graphql'))
const resolversArray = loadFilesSync(path.join(__dirname, './**/*.resolver.ts'))
const typeDefs = mergeTypeDefs(typesArray)
const resolvers = mergeResolvers(resolversArray)
export { typeDefs, resolvers }
glob pattern 이용 File loading 후 merge 하는 작업 처리
import { typeDefs, resolvers } from '~/src/graphql'graphql-tools Schema 구성
graphql/index.ts
import path from 'path'
import { loadFilesSync } from '@graphql-tools/load-files'
import { mergeTypeDefs, mergeResolvers } from '@graphql-tools/merge'
const typesArray = loadFilesSync(path.join(__dirname, './**/*.graphql'))
const resolversArray = loadFilesSync(path.join(__dirname, './**/*.resolver.ts'))
const typeDefs = mergeTypeDefs(typesArray)
const resolvers = mergeResolvers(resolversArray)
export { typeDefs, resolvers }
glob pattern 이용 File loading 후 merge 하는 작업 처리
import { typeDefs, resolvers } from '~/src/graphql'Simple Resolver
const resolver = {
Query: {
events: (_, args, info, context) => [
{
id: 'cewf2',
name: 'test',
mark: false,
},
{
id: 'cewf3',
name: 'test',
mark: false,
},
],
},
}
export default resolver
TypeScript 사용시 SDL에서 정의했던 Type을
Typescript 인터페이스에 맞춰서 다시 정의
GraphQL Code Generator
Merge된 GraphQL defintion기반으로 Code Type을 생성
export type EventResolvers<
ContextType = any,
ParentType extends ResolversParentTypes['Event'] = ResolversParentTypes['Event']
> = {
id?: Resolver<ResolversTypes['String'], ParentType, ContextType>
name?: Resolver<ResolversTypes['String'], ParentType, ContextType>
mark?: Resolver<ResolversTypes['Boolean'], ParentType, ContextType>
createdAt?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>
updatedAt?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>
__isTypeOf?: isTypeOfResolverFn<ParentType>
}
GraphQL Code Generator

Resolver에 대한 Type Definition import 해서 사용
Resolver 이해
GQL Type field 에 대해 데이터를 채우는 함수
Datasource 는 DB혹은 third-part api 등 자유롭게 사용
type Query {
numberSix: Int! # Should always return the number 6 when queried
numberSeven: Int! # Should always return 7
}const resolvers = {
Query: {
numberSix() {
// fetch DB or API call
return 6;
},
numberSeven() {
return 7;
}
}
};Arguments Handle
id를 받아서 User를 return Query
type User {
id: ID!
name: String
}
type Query {
user(id: ID!): User
}
// mock data
const users = [
{
id: '1',
name: 'Elizabeth Bennet'
},
{
id: '2',
name: 'Fitzwilliam Darcy'
}
];Resolver arguments
Parents, args, context, info 4가지 인자를 받을 수 있음
const resolvers = {
Query: {
user(parent, args, context, info) {
return users.find(user => user.id === args.id);
}
}
}Parents, args, context, info 4가지 인자를 받을 수 있음
Parents, args, context, info 4가지 인자를 받을 수 있음
Resolver chain
# A library has a branch and books
type Library {
branch: String!
books: [Book!]
}
# A book has a title and author
type Book {
title: String!
author: Author!
}
# An author has a name
type Author {
name: String!
}
type Query {
libraries: [Library]
}Resolver 는 GraphQL Field의 데이터를 채우는 function
GQL Server는 들어온 Query를 분석하고 field별로 resolver 를 호출
Resolver chain
query GetBooksByLibrary {
libraries {
books {
author {
name
}
}
}
}위와 같은 순서로 Resolver chain 을 타게됨

Hello, GraphQL (part2)
By y0c
Hello, GraphQL (part2)
GrpahQL 구현 부분
- 194