이번에는 Apollo Server
를 활용해 GraphQL-API
를 제공하는 서버를 개발해 보겠습니다.
Apollo Server
는 GraphQL-API를 제공하는 서버를 개발할 수 있게 도와주는 패키지로서 기존에 Node.js에서 사용하는 Express와 역할이 비슷합니다.
먼저 새로운 폴더 05-01-graphql-api-with-apollo-server-1
을 만들어주세요.
yarn init
명령어를 입력해 package.json
파일을 생성해주세요.
GraphQL-API
를 사용할 것이기 때문에 yarn add graphql
명령어를 입력해 설치해주세요.
Apollo Server는 GraphQL이 적용된 서버를 생성할 수 있는 클래스입니다.
yarn add apollo-server graphql
을 입력해 설치해주세요.
{
"name": "05-01-graphql-api-with-apollo-server-1",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"type": "module",
"dependencies": {
"apollo-server": "^3.12.0",
"graphql": "^16.6.0"
}
}
그 후 index.js
파일을 생성해주고 설치했던 apollo-server
와 graphql
을 불러와줍니다.
import { ApolloServer, gql } from 'apollo-server'
graphql
에는 2가지 요청이 있습니다.
apollo-server
는 typeDef
와 resolver
를 인자로 받아 서버를 생성합니다.
const myResolvers = {
Query: {
hello: () => 'world',
}
}
myResolvers
객체 안에 Query
라는 객체를 선언하였습니다.
Query
객체 안에 world라는 문자열을 반환하는 로직을 구현하였습니다.
const myTypeDefs = gql`
type Query {
"A simple type for getting started!"
hello: String
}
`
myTypeDefs
객체 안에 type Query
객체를 선언해 반환될 데이터의 형태를 지정해 주었습니다.
const server = new ApolloServer({
typeDefs: myTypeDefs,
resolvers: myResolvers
});
server.listen(3000);
이제 apollo server
를 생성하고 typeDef
와 myTypeDefs
를, resolver
와 myResolvers
를 연결시켜주고 3000번 포트를 열어줍니다.
node index.js
를 입력해 apollo server
를 실행시켜줍니다.
서버가 실행되면 위와 같은 화면을 볼 수 있습니다.
Query your server를 클릭해 playground에 들어가주세요.
query {
hello
}
위와 같이 입력하고 실행하면 myResolvers
라는 객체 안에 Query
객체에 작성해 놓은 hello
함수가 실행되면서 myTypeDefs
객체 안에 type Query
객체에 지정해 놓은 데이터의 형태로 반환됩니다.
이번엔 본격적으로 Rest-API로 개발했던 로직을 GraphQL-API을 사용해서 개발해보겠습니다.
05-02-graphql-api-with-apollo-server-2
폴더를 만들어줍니다.
package.json
은 05-01-graphql-api-with-apollo-server-1
에 있는 것을 복사 붙여넣기 해주세요.
04-02-rest-api-with-express-2
의 동일한 기능의 로직을 GraphQL-API
로 사용해보는 것이기 때문에 참고하시면서 개발을 진행해주시면 됩니다.
const myResolvers = {
Query: {
fetchBoards: () => {
// 데이터 조회하는 로직
return '조회에 성공하였습니다.'
}
}
}
Rest-API의 GET 메서드는 GraphQL-API에서는 모두 Query로 동작합니다.
Query
를 이용해 다음과 같이 데이터를 조회하는 기능을 구현해 주세요.
const myTypeDefs = gql`
type Query {
fetchBoards: String
}
`
myTypeDefs
객체 안에 type Query
안에는 반환될 데이터의 타입을 지정해 주었습니다.
반환될 데이터의 종류가 String이기 때문에 다음과 같이 타입을 지정했습니다.
const server = new ApolloServer({
resolvers: myResolvers,
typeDefs: myTypeDefs
});
server.listen(3000);
이제 resolvers
와 typeDefs
를 연결시켜주고 서버를 열어줍니다.
그 후 브라우저를 통해 접속하고 테스트 해보겠습니다.
query {
fetchBoards
}
myResolvers
의 Query
객체에 작성해 놓은 fetchBoards
함수가 실행되면서 type Query
에 지정해 놓은 문자열 메세지가 잘 출력됩니다.
const myResolvers = {
Mutation: {
createBoard: () => {
// 데이터 등록하는 로직
return '등록에 성공하였습니다.'
}
}
}
Rest-API의 CRUD는 모두 GraphQL-API에서는 Mutation
으로 동작합니다.
myResolvers
안에 Mutation
을 추가해주세요.
Mutation
에는 다음과 같이 데이터를 등록하는 로직을 구현해 주세요.
const myTypeDefs = gql`
type Mutation {
createBoard: String
}
`
myTypeDefs
객체 안에 tyoe Query
객체를 선언해 반환될 데이터의 형태를 지정해 주었습니다.
다시 서버를 재시작해주고 동일하게 접속해 테스트를 해보겠습니다.
mutation {
createBoard
}
myResolvers
라는 객체 안에 Mutation
객체에 작성해 놓은 createBoard
함수가 실행되면서 type Mutation
에 지정해 놓은 문자열 메세지가 잘 출력됩니다.
05-03-graphql-api-with-apollo-server-3
폴더를 생성해주고 05-02-graphql-api-with-apollo-server-2
폴더에 있는 파일들을 모두 복사 후 붙여넣어주세요.
yarn install
명령어를 입력해서 package.json
에 기록되어 있는 패키지들을 설치해줍니다.
이전에는 Rest-API의 GET 메서드를 사용해서 데이터를 조회 후 반환해 주었습니다.
동일한 데이터를 반환하는 기능을 GraphQL-API로 변경해보도록 하겠습니다.
const myResolvers = {
Query: {
fetchBoards: () => {
// 데이터 조회하는 로직
// return '조회에 성공하였습니다.'
return [
{ number: 1, writer: '철수', title: '제목입니다~', contents: '내용!!'},
{ number: 2, writer: '영희', title: '안녕하세요~', contents: '배고프네요'},
{ number: 3, writer: '훈이', title: '점심은 맛있게 드셨나요?', contents: '식사는 하셨나요?'},
{ number: 4, writer: '맹구', title: '안녕하세요!!!', contents: '내용입니다~'}
]
}
}
}
Query
내부에 데이터를 조회하는 로직을 작성해 주세요.
Rest-API에서는 res.send
를 이용해서 데이터를 반환했는데 GraphQL-API에서는 return
을 사용해 함수를 종료하면서 데이터를 반환합니다.
myResolvers
에 작성한 Query
의 fetchBoards
함수의 반환값을 한번 확인해 보세요.
const myTypeDefs = gql`
type MyBoard {
number: Int
writer: String
title: String
contents: String
}
type Query {
fetchBoards: [MyBoard]
}
`
반환 값은 배열 안에 객체의 형태로 이루어져 있습니다.
먼저 객체의 타입을 지정해주기 위해서 type MyBoard {}
을 선언해주고 요소의 타입을 지정해주세요.
객체안의 요소들의 타입을 지정해 주었다면 MyBoard
를 배열로 감싸서 fetchBoards: [MyBoard]
과 같이 지정해 줍니다.
다시 서버를 재시작 해주고 동일하게 테스트를 해보겠습니다.
query {
fetchBoards {
number
writer
title
contents
}
}
myResolvers
라는 객체 안에 Mutation
객체에 작성해 놓은 createBoard
함수가 실행되면서 type Mutation
에 지정해 놓은 타입과 type MyBoard
가 지정해 놓은 요소들의 타입에 맞게 응답받았습니다.
const myResolvers = {
Mutation: {
createBoard: (_, args) => {
// 데이터 등록하는 로직
console.log('입력값들: ', args);
console.log('입력값들2: ', args.createBoardInput);
return '등록에 성공하였습니다.'
}
}
}
05-02-graphql-api-with-apollo-server-2
폴더의 index.js
의 createBoard
와 동일하지만 매개변수가 다르게 사용되어 있는 것을 확인할 수 있습니다.
4개의 매개변수가 있는데 다음과 같습니다.
parent
: 부모 타입 리졸버에서 반환된 결과를 가진 객체args
: 쿼리 요청 시 전달된 파라미터를 가진 객체context
: GraphQL의 모든 리졸버가 공유하는 객체로서 로그인 인증, 데이터베이스 접근 권한 등에 사용info
: 명령 실행 상태 정보를 가진 객체Rest-API
에서는 요청 데이터를 확인하기 위해 Parameter(매개변수) req를 사용했습니다.
GraphQL-API
에서는 Parameter(매개변수) args를 사용해 요청된 데이터를 확인할 수 있습니다.
만약 사용하지 않는 파라미터를 사용할때는 _
(언더바) 를 파라미터로 사용합니다.
const myTypeDefs = gql`
input CreateBoardInput {
writer: String
title: String
contents: String
}
type Mutation {
createBoard(createBoardInput: CreateBoardInput): String
}
`
이번에는 요청 데이터 타입을 지정해 보겠습니다.
다음과 같이 input
을 사용해 데이터의 타입을 지정합니다.
다시 서버를 재시작해주고 동일하게 테스트를 해보겠습니다.
mutation {
createBoard(createBoardInput: {
writer: "yeontan0826",
title: "this is title",
contents: "this is contents!!"
})
}
input CreateBoardInput
에 지정해 놓은 요소들의 데이터 타입에 맞게 요청을 보내야 합니다.
myResolvers
라는 객체 안에 Mutation
객체에 작성해 놓은 createBoard
함숙 실행되면서 type Mutation
에 지정해 놓은 문자열 메세지가 잘 출력됩니다.
아까 리졸버에서 등록한 콘솔에도 잘 찍힌 것을 확인할 수 있습니다.
05-04-graphql-api-with-apollo-server-4
폴더를 생성해주고 05-03-graphql-api-with-apollo-server-3
폴더에 있는 파일들을 모두 복사 후 붙여넣어주세요.
이번에는 04-04-rest-api-with-express-3-token
Rest-API로 개발했던 인증번호를 전송해주는 API를 GraphQL-API로 변경해보겠습니다.
04-04-rest-api-with-express-3-token
의 phone.js
파일을 복사해서 05-04-graphql-api-with-apollo-server-4
폴더 안에 붙여넣어주세요.
import { checkValidatePhone, getToken, sendTokenToSMS } from './phone.js'
phone.js
파일에 있는 함수들을 import
해주세요.
const myResolvers = {
Mutation: {
createTokenOfPhone: (_, args) => {
/// 1. 휴대폰 번호 자릿수 맞는지 확인하기
const isValidationPhone = checkValidatePhone(args.phone);
if(isValidationPhone === true) {
// 2. 핸드폰 토큰 6자리 만들기
const token = getToken(6);
// 3. 핸드폰번호에 토큰 전송하기
sendTokenToSMS(args.phone, token);
}
return '인증번호를 전송했습니다.';
}
}
}
Mutation
에 요청이 왔을 때 휴대전화로 인증번호를 보내주는 로직을 다음과 같이 추가헤 주세요.
const myTypeDefs = gql`
type Mutation {
createTokenOfPhone(phone: String!): String
}
`
이제 요청 데이터 타입을 지정하는데 ! : Not Nullable
을 지정하여 핸드폰 번호를 필수 값과 타입을 지정하였습니다.
다시 서버를 실행시키고 테스트를 해보도록 하겠습니다.
mutation {
createTokenOfPhone(phone: "01012345678")
}
input CreateBoardInput
필수 요소인 phone
을 입력해서 요청을 보내면 인증번호를 성공적으로 전송했다는 응답을 받게 됩니다.
만약 필수 요소인 phone
을 요청하지 않는다면 에러를 확인할 수 있습니다.