apollo-server-express
서버를 생성했으면 서버에 resolver
와 typeDef
를 포함한 schema
를 등록해야 한다. resolver
와 typeDef
에 대한 설명은 GraphQL이란? 포스팅 참조.
GraphQL이란? 포스팅에서 설명했으므로 설명은 생략하고 아래와 같이 TypeDef
와 Resolver
를 작성해준다.
// \resolver\character\character.typeDefs.ts
import { gql } from "apollo-server-express";
export default gql`
type Query {
"""
단일 Character 조회
"""
character(id: Int!): Character
"""
모든 Character 목록 조회
"""
allCharacters: [Character]
}
type Mutation {
"""
Character 생성
"""
createCharacter(name: String!, appearsIn: [ID!]!): Character
}
"""
등장인물 Type
"""
type Character {
"""
등장인물 식별번호
"""
id: ID!
"""
등장인물 이름
"""
name: String!
"""
등장인물 등장 회차정보
"""
appearsIn: [Episode]!
}
`;
TypeDef를 작성할 때는 apollo-server-express
의 gql
을 import 해서 백틱(`) 안에 작성해야한다. 즉 string이라는 건데 VSCode를 사용한다면 Apollo GraphQL
extension을 설치하면 일반 코드처럼 자동완성이나 색깔 구분도 되고 오류도 잡아준다.
이제 Resolver
도 작성해 주는데 나는 query
와 mutation
을 나눠서 작성했다.
// \resolver\character\character.queries.ts
import { GraphQLError } from "graphql";
// prisma-client
import client from "../../client";
export default {
Query: {
/**
* 단일 Character 조회
* @param {ID} id: 등장인물 식별번호
* @returns 등장인물 정보
*/
character: (_, { id }) => {
return client.Character.findFirst({
where: {
id,
},
});
},
allCharacters: () => {
return client.Character.findMany();
},
},
};
// \resolver\character\character.mutations.ts
import { GraphQLError } from "graphql";
// prisma-client
import client from "../../client";
export default {
Mutation: {
/**
* Character 생성
* @param {string} : 등장인물 식별번호
* @param {number[]} appearsIn: 등장인물 식별번호
* @returns 생성된 등장인물 정보
*/
createCharacter: (_, { name, appearsIn }) => {
return client.Character.create({
data: {
name,
appearsIn: { connect: appearsIn.map(id => ({ id })) }
},
});
},
},
};
참고로 GraphQL은
Query
는 여러 query가 하나의 operation으로 올 경우 동시에 쿼리를 실행하고Mutation
은 순차대로 진행한다. 즉, 비동기 처리가 어느정도 자동으로 되서 return 정보를 async/await 처리 해주지 않아도 된다. (그러나 메소드 내에서의 비동기 처리는 해줘야 한다. Promise 객체가 return될 때만 자동으로 비동기처리를 해준다는 뜻.)
이제 위에 작성해준 Resolver
와 TypeDefs
를 합쳐서 하나의 Schema
로 만들어 준다.
// 원하는 디렉토리에 schema.ts 생성
const { makeExecutableSchema } = require('@graphql-tools/schema');
const { mergeTypeDefs, mergeResolvers } = require('@graphql-tools/merge');
const { loadFilesSync } = require('@graphql-tools/load-files');
const loadedTypes = loadFilesSync(`${__dirname}/**/*.typeDefs.ts`);
const loadedResolvers = loadFilesSync(
[
`${__dirname}/**/*/*.{queries,mutations}.ts`
]
);
const typeDefs = mergeTypeDefs(loadedTypes);
const resolvers = mergeResolvers(loadedResolvers);
const schema = makeExecutableSchema({
typeDefs,
resolvers
});
export default schema;
참고로 __dirname은 현재 파일의 절대 경로를 출력하는 변수로 경로가 다를 경우 각자 경로에 맞게 변경해주면 된다.
마지막으로 작성한 Schema
를 apollo-server-express
에 적용해준다
const express = require('express');
const { ApolloServer, gql } = require('apollo-server-express');
const schema = require('./schema').default;
const app = express();
/**
* Apollo server 생성
*/
const server = new ApolloServer({
// 작성한 스키마 적용
schema,
playground: true,
context: ({ req, res }) => {
return { req, res };
},
formatError: (err) => {
console.log(err);
return err;
}
});
/**
* Express app에 Apollo 연결
*/
const con = async() => {
await server.start();
server.applyMiddleware({ app, path: "/graphql" });
}
con();
/**
* application 실행 확인
*/
app.listen({port: 4000}, () => {
console.log('Now browse to http://localhost:4000' + server.graphqlPath)
})
이제 서버를 실행시킨후 http://localhost:4000/graphql
로 요청을 보내면 응답이 올 것이다.
Playground
에 들어가보면 작성된 typeDef
와 resolver
를 자동으로 인식해서 클릭 몇번이면 테스트를 해볼 수 있다.