4 Server - GraphQL
source: categories/study/react_restapi_graphql/react_restapi_graphql5.md
4.1 GraphQL 환경세팅 및 schema 정의
4.1.1 GraphQL 환경세팅
이번 시간에는 REST API
로 작성한 서버를 GraphQL
로 변환시켜보겠습니다.
server
폴더로 이동해서 설치를 먼저 해보겠습니다.
yarn add apollo-server apollo-server-express graphql
설치가 다 되었다면 src/index.js
파일을 열어보겠습니다.
// server/src/index.js
import express from 'express'
// graphQL용 서버는 ApolloServer라는걸 이용할겁니다.
import {ApolloServer} from "apollo-server-express";
import cors from 'cors'
import messagesRoute from "./routes/messages.js";
import usersRoute from "./routes/users.js";
const app = express();
// ApolloServer를 사용하면 아래 2줄 코드는 필요가 없게됩니다.
// app.use(express.urlencoded({extended: true}))
// app.use(express.json()) // express에서 json 형태로 사용하겠다.
app.use(cors({
origin: 'http://localhost:3000', // 클라이언트 서버
credentials: true,
}))
const routes = [...messagesRoute, ...usersRoute]
routes.forEach(({method, route, handler}) => {
app[method](route, handler)
})
// 서버 경로는 8000번
app.listen(8000, () => {
console.log('server listening on 8000...')
})
graphQL
과 REST API
의 가장 큰 차이점은
REST API
서버는route
를 이용해서 사용자가 요청한route
에 따라서 그에 대응하는response
를 내려주는 형태였다면graphQL
서버는 오직graphQL
이라는path
하나로..route
로치면/graphql
이라는path
하나로 다 들어와서 그 안에서graphQL
내부에서 자체적으로 판단해서 나누도록 되어있습니다.
즉, graphQL
은 REST API
에서 route
로 처리하던 것을 이제는 resolvers
라는 개념을 통해 처리합니다.
resolvers
라는 것을 이용해서 graphQL
요청한 데이터들이 나눠지게됩니다.
// server/src/index.js
import express from 'express'
// graphQL용 서버는 ApolloServer라는걸 이용할겁니다.
import {ApolloServer} from "apollo-server-express";
import cors from 'cors'
import messagesRoute from "./routes/messages.js";
import usersRoute from "./routes/users.js";
const app = express();
// ApolloServer를 사용하면 아래 2줄 코드는 필요가 없게됩니다.
// app.use(express.urlencoded({extended: true}))
// app.use(express.json()) // express에서 json 형태로 사용하겠다.
app.use(cors({
origin: 'http://localhost:3000', // 클라이언트 서버
credentials: true,
}))
// ApolloServer 서버를 생성합니다.
const server = new ApolloServer({
// typeDefs 라는 것이 필요하고, schema(스키마)라는게 필요합니다.
typeDefs: schema,
// 그리고 resolvers이라는 정보가 필요하게됩니다.
resolvers,
// 그리고 resolvers가 참조할 데이터.. 즉, DB 역할을하는 models 부분이 정의가 되어있어야 합니다.
context: {
models: {
// 아래 정의에 대해선 이따 하도록 하겠습니다.
messages: '',
users: '',
}
}
});
// 위 정의가 다 되고나면, server.applyMiddleware() 함수를 통해서 아래와 같이 코드를 작성합니다.
server.applyMiddleware({ app, path: '/graphql' })
// 서버 경로는 8000번
app.listen(8000, () => {
console.log('server listening on 8000...')
})
REST API
에서 route
가 하던 역할을 GraphQL
에선 resolvers
가 한다는 느낌으로 가져가시면됩니다.
4.1.2 schema 정의
4.1.2.1 messages 스키마 정의
schema
폴더 및 messages.js
파일 생성
// server/src/schema/messages.js
import {gql} from "apollo-server-express";
// ------------------------------------------------------------------------------------------------
// -------------------------------- 공통 설명
// gql은 아래와같이 태그드 탬플릿 문법을 사용합니다.
// `` 문자열 탬플릿 내용을 gql(graphql)로 인식합니다.
// `` 문자열 탬플릿 안에는 gql 관련 명령어를 넣어주면됩니다.
// 그럼 `` 문자열 탬플릿 안에 있는 gql 명령어를 gql이 인식해서 자바스크립트 언어로 치환을 해줍니다.
// ------------------------------------------------------------------------------------------------
// -------------------------------- type Message { ... } 설명
// 문자열 안에서 type을 정의해줄게요.
// 여기서는 typescript와는 다르게 graphQL 고유의 타입 정의가 있습니다.
// ! 느낌표는 반드시 들어가야된다 라는 뜻입니다.
// ID는 고유값, 유니크한 값이라는 것을 명시해주는 것입니다.
// text는 String, S <- 대문자로 들어갑니다.
// user는 User!
// timestamp는 13자리 숫자였거든요. graphQL에서는 13자리 정수형 숫자를 인식하질 못합니다.
// 그래서 Float이라고 적겠습니다. 정수형 자릿수의 제한이 있기 때문에 Float이라고 지정하는 겁니다.
// ------------------------------------------------------------------------------------------------
// -------------------------------- extend type Query { ... } 설명
// extend type Query의 messages에서 cursor값이 없을수도 있기 때문에 ID값 뒤에 ! 느낌표를 붙이지 않았습니다.
// Message가 반드시 와야돼고(!) 그 Message가 배열 형태로 이루어져있고 또 그 배열이 반드시 와야된다(!). 그런 데이터 형태다!
// 그리고 message 단독 요청일 경우! ID값이 반드시 와야됩니다.(!)
// 위와 같은 Query문을 날리게 될겁니다.
// 위 Query문은 REST API로 치자면 GET 메소드라고 생각하시면됩니다.
// messages Query문을 날리면 기존의 getMessages 개념과 같고,
// message는 getMessage 개념과 같다고 보시면됩니다.
// ------------------------------------------------------------------------------------------------
// -------------------------------- extend type Mutation { ... } 설명
// Mutation이란 의미는 '변화를 일으키다'라는 의미입니다.
// create, update, delete가 모두 여기에 해당합니다.
// create: 새로운 글을 쓸테니까 text: String!, 그리고 user 정보가 필요하니까 ID! 그리고 이때 응답값은 Message!가 될겁니다.
// update: id를 지정해줘야되니까 ID!, 수정할 내용 text: String!, 그리고 userId: ID!, 똑같이 Message!가 응답값으로 넘어올겁니다.
// delete: delete 메소드 같은 경우는 id만 있으면돼고, userId: ID! 가 필요하겠죠? 그리고 이때의 응답값은 Message의 ID! 값이었습니다.
const messageSchema = gql`
type Message {
id: ID!
text: String!
user: User!
timestamp: Float
}
extend type Query {
messages(cursor: ID): [Message!]!
message(id: ID!): Message!
}
extend type Mutation {
createMessage(text: String!, userId: ID!): Message!
updateMessage(id: ID!, text: String!, userId: ID!): Message!
deleteMessage(id: ID!, userId: ID!): ID!
}
`
// id는 uuid의 v4() 함수로 생성한 고유한 id
// userId는 jay, roy 이런거
export default messageSchema
// 이러한 형태로 schema를 지정을 했습니다.
// 이 schema에 있는 데이터 정보, 형태 그대로 graphQL 요청에 의한 응답값이 오게됩니다.
// 미리 이런 정의를 해놓은 상태에서, 위와 같은 형태의 응답값이 오게끔 resolvers를 만들어주면됩니다.
// resolvers에서 만들어준 내용이 변경이 필요하면, resolvers도 변경하면서 같이 위 schema도 맞춰주는 식으로 하면 됩니다.
4.1.2.2 users 스키마 정의
// server/src/schema/users.js
import {gql} from 'apollo-server-express'
const userSchema = gql`
type user {
id: ID!
nickname: String!
}
extend type Query {
users(): [User!]!
user(id: ID!): User
}
`
export default userSchema
// -------------------------------------------- query문
// users() 요청보내면
// {
// "roy": {"id": "roy", "nickname": "로이"},
// "jay": {"id": "jay", "nickname": "제이"}
// }
// 이 값들이 응답옴.
// 하지만 graphQL에선 object 형태로 받을 순 없나봄.
// 위와 같이 [User!]! <- 이렇게 배열 형태로 받도록 해야됨.
// user(id: ID!): User 이거는 하나만 오는거니깐 "roy": {"id": "roy", "nickname": "로이"} <- 이런식으로 응답오는건가?
4.1.2.3 server/src/schema/index.js
똑같은 타입 정의가 중첩되지 않게끔 하기 위해서 extend
라는 개념을 쓴거고, 그거를 한데 묶기위해서 schema/index.js
같은거를 만들어 놓은겁니다.
// server/src/schema/index.js
// messages.js, users.js 이 둘을 한데 아우르는 index.js 파일을 만듭니다.
import {gql} from "apollo-server-express";
import messageSchema from "./messages";
import userSchema from "./users";
// linkSchema를 만듭니다.
// 아래는 default로 주는 정보입니다.
// 방금 messages.js 파일 같은데 보시면 extend라고 있었거든요?
// type Query 정의를 기본적으로 index.js에서 한꺼번에 몰아서 해주기위해서 의미없는 정보를 하나 준겁니다.
const linkSchema = gql`
type Query {
_: Boolean
}
type Mutation {
_: Boolean
}
`
export default [linkSchema, messageSchema, userSchema]
// 일단 지금은, 스키마를 만들 때 index.js는 여러가지를 취합하고 linkSchema 같은게 하나 추가가되면 동작이 잘 되더라 라고 인식하시고 넘어가시면 되겠습니다.
// server/src/schema/users.js
import {gql} from 'apollo-server-express'
const userSchema = gql`
type User {
id: ID!
nickname: String!
}
extend type Query {
users: [User!]!
user(id: ID!): User
}
`
export default userSchema
// -------------------------------------------- query문
// users() 요청보내면
// {
// "roy": {"id": "roy", "nickname": "로이"},
// "jay": {"id": "jay", "nickname": "제이"}
// }
// 이 값들이 응답옴.
// 하지만 graphQL에선 object 형태로 받을 순 없나봄.
// 위와 같이 [User!]! <- 이렇게 배열 형태로 받도록 해야됨.
// user(id: ID!): User 이거는 하나만 오는거니깐 "roy": {"id": "roy", "nickname": "로이"} <- 이런식으로 응답오는건가?
// server/src/schema/messages.js
import {gql} from "apollo-server-express";
// ------------------------------------------------------------------------------------------------
// -------------------------------- 공통 설명
// gql은 아래와같이 태그드 탬플릿 문법을 사용합니다.
// `` 문자열 탬플릿 내용을 gql(graphql)로 인식합니다.
// `` 문자열 탬플릿 안에는 gql 관련 명령어를 넣어주면됩니다.
// 그럼 `` 문자열 탬플릿 안에 있는 gql 명령어를 gql이 인식해서 자바스크립트 언어로 치환을 해줍니다.
// ------------------------------------------------------------------------------------------------
// -------------------------------- type Message { ... } 설명
// 문자열 안에서 type을 정의해줄게요.
// 여기서는 typescript와는 다르게 graphQL 고유의 타입 정의가 있습니다.
// ! 느낌표는 반드시 들어가야된다 라는 뜻입니다.
// ID는 고유값, 유니크한 값이라는 것을 명시해주는 것입니다.
// text는 String, S <- 대문자로 들어갑니다.
// user는 User!
// timestamp는 13자리 숫자였거든요. graphQL에서는 13자리 정수형 숫자를 인식하질 못합니다.
// 그래서 Float이라고 적겠습니다. 정수형 자릿수의 제한이 있기 때문에 Float이라고 지정하는 겁니다.
// ------------------------------------------------------------------------------------------------
// -------------------------------- extend type Query { ... } 설명
// extend type Query의 messages에서 cursor값이 없을수도 있기 때문에 ID값 뒤에 ! 느낌표를 붙이지 않았습니다. ---- 여기서 ID는 jay, roy같은거..
// Message가 반드시 와야돼고(!) 그 Message가 배열 형태로 이루어져있고 또 그 배열이 반드시 와야된다(!). 그런 데이터 형태다!
// 그리고 message 단독 요청일 경우! ID값이 반드시 와야됩니다.(!) ---- 여기서 id는 uuid 라이브러리의 v4() 함수로 생성되는 그런 id...
// 위와 같은 Query문을 날리게 될겁니다.
// 위 Query문은 REST API로 치자면 GET 메소드라고 생각하시면됩니다.
// messages Query문을 날리면 기존의 getMessages 개념과 같고,
// message는 getMessage 개념과 같다고 보시면됩니다.
// ------------------------------------------------------------------------------------------------
// -------------------------------- extend type Mutation { ... } 설명
// Mutation이란 의미는 '변화를 일으키다'라는 의미입니다.
// create, update, delete가 모두 여기에 해당합니다.
// create: 새로운 글을 쓸테니까 text: String!, 그리고 user 정보가 필요하니까 ID! 그리고 이때 응답값은 Message!가 될겁니다.
// update: id를 지정해줘야되니까 ID!, 수정할 내용 text: String!, 그리고 userId: ID!, 똑같이 Message!가 응답값으로 넘어올겁니다.
// delete: delete 메소드 같은 경우는 id만 있으면돼고, userId: ID! 가 필요하겠죠? 그리고 이때의 응답값은 Message의 ID! 값이었습니다.
const messageSchema = gql`
type Message {
id: ID!
text: String!
userId: ID!
timestamp: Float
}
extend type Query {
messages: [Message!]!
message(id: ID!): Message!
}
extend type Mutation {
createMessage(text: String!, userId: ID!): Message!
updateMessage(id: ID!, text: String!, userId: ID!): Message!
deleteMessage(id: ID!, userId: ID!): ID!
}
`
// id는 uuid의 v4() 함수로 생성한 고유한 id
// userId는 jay, roy 이런거
export default messageSchema
// 이러한 형태로 schema를 지정을 했습니다.
// 이 schema에 있는 데이터 정보, 형태 그대로 graphQL 요청에 의한 응답값이 오게됩니다.
// 미리 이런 정의를 해놓은 상태에서, 위와 같은 형태의 응답값이 오게끔 resolvers를 만들어주면됩니다.
// resolvers에서 만들어준 내용이 변경이 필요하면, resolvers도 변경하면서 같이 위 schema도 맞춰주는 식으로 하면 됩니다.
4.2 resolver 정의
그 다음에 route
를 대체할 resolver
를 만들어보겠습니다.
이 부분이 중요하겠죠?
routes/messages.js
파일에서 작성한 코드가 하던 역할을 resolvers/messages.js
파일 코드에서 그대로 한다고 보시면됩니다.
routes/messages.js
파일 내용을 그대로 복붙하고 수정해보겠습니다.
// server/src/resolvers/messages.js
import {v4} from "uuid";
// readDB는 지우고 writeDB만 가지고 가겠습니다.
import {writeDB} from "../dbController.js";
// setMsgs는 graphQL 서버에서도 사용하므로 살립니다.
const setMsgs = data => writeDB('messages', data)
//-------------------------------------------------------------------
// 그리고 나머지는 유지한 상태에서 messageResolver를 만들겠습니다.
// 이 messageResolver는 Query 명령에 대해서 정의를 하고,
// 또 Mutation 명령에 대해서 정의를 할겁니다.
// 이때 schema에서 정의했던 명령어들, 그 내용을 그대로 쓰면됩니다.
// schema에서 정의했던 명령어들에 대한 본격적인 정의가 여기서 이뤄집니다.
// 아래와 같은 구조로 정의를 해줍니다.
// 각각의 resover에는 parent, args, context라는 세개의 인자가 들어옵니다. 이들 각각에 대해서 graphQL 공식 사이트에선 이렇게 설명을 하고 있습니다.
/**
* parent: 대부분 사용되지 않는 root query type의 이전 객체, parent 객체라고 하고, 거의 사용되지 않습니다.
* args: graphQL Query에 필요한 필드에 제공되는 인수(parameter)가 들어있는 객체
* context: 로그인한 사용자 정보, DB Access 권한 등의 중요한 정보들을 담고있는 객체
* 위 각각에 어떤 내용이 들어가있는지는 나중에 따로 살펴보도록 하겠습니다.
* */
const messageResolver = {
Query: {
// 일단은 아래 messages의 인자들은 messages를 가져오는 '정보'니깐, 필요한 부분은 3번째 인자인 context입니다.
// context 안에 models라는 것이 들어있는데, 이 models는 아까 index.js에서 정의한.. 저희가 직접 정의한 것입니다.
messages: (parent, args, {db}) => {
// console.log({obj, args, context}) // 각 인자에 어떤 내용이 들어가있는지는 나중에 따로 살펴보도록 하겠습니다.
// 아래와 같이 db.messages라고 정의하고 일단은 넘어가도록 하겠습니다.
return db.messages
},
// message를 하나 불러오는 Query입니다.
// 2번째 인자에서 Query에 필요한 parameter 값이 온다고 했죠? 여기에 id 값이 들어오게 될겁니다.
// id가 없을 경우를 대비해 default 값을 넣어줬습니다.
// 그리고 마지막 3번째 인자로 마찬가지로 db가 들어올겁니다.
message: (parent, {id = ''}, {db}) => {
// 그럼 db.messages에서 id가 2번째 인자로 넘어온 id와 일치하는 것을 찾아주면 될겁니다.
return db.messages.find(msg => msg.id === id)
},
},
Mutation: {
// 2번째 데이터, param 데이터들..
// REST API의 create 메소드를 보면, 저희에게 필요했던 데이터가 body에 있는 text와 userId였죠?
// 그냥 body에서 왔는지, params에서 왔는지, query에서 왔는지 신경쓸거 없이 아래처럼 2번째 인자에 text, userId를 다 받아오면됩니다.
// graphQL은 모두 뭉뜽그려서 한군데서 처리하면된다 라고 생각하시면 됩니다.
createMessage: (parent, {text, userId}, {db}) => {
// 그러면 create에 있는 내용은 사실상 REST API에 있는거랑 똑같은거죠.
// const msgs = getMsgs(); // getMsgs는 db에있는 messages를 사용하면되니깐 필요가 없고
const newMsg = {
id: v4(),
text, // 여기는 body.text 이렇게 올 필요가 없으므로 왼쪽과 같이 수정하면 됩니다.
userId, // 여기도 body 필요없으므로 마찬가지로..
timestamp: Date.now(),
}
db.messages.unshift(newMsg) // msgs.unshift(newMsg) <- 이 코드 대신 왼쪽 코드로 작성해주고
setMsgs(db.messages) // setMsgs(msgs); <- 이걸 왼쪽과 같이 수정
return newMsg // res.send(newMsg) <- 이걸 return newMsg로 수정
},
// update에선 params에 들어오는 id가 필요했었다. body.text, body.userId(roy, jay)도 필요했었다.
// 2번째 인자에서 이 3개를 뭉뜽그려서 꺼내옵니다.
// 그리고 3번재 인자에선 마찬가지로 db를 꺼내옵니다.
updateMessage: (parent, {id, text, userId}, {db}) => {
// REST API의 update 내용을 가져옵니다.
// msgs는 db.messages로 수정합니다.
const targetIndex = db.messages.findIndex(msg => msg.id === id)
if (targetIndex < 0) throw Error('메시지가 없습니다.') // 일단 try catch 구문을 사용안해서, Error 객체를 통한 에러처리는 여기서 보내겠습니다.
if (db.messages[targetIndex].userId !== userId) throw Error('사용자가 다릅니다.') // 일단 try catch 구문을 사용안해서, Error 객체를 통한 에러처리는 여기서 보내겠습니다.
const newMsg = {
...db.messages[targetIndex],
text,
}
db.messages.splice(targetIndex, 1, newMsg)
setMsgs(db.messages)
return newMsg; // res.send(newMsg) 이게아니라 그냥 return을 해주면됩니다.
},
// delete도 REST API에선 params의 id와 queryString의 userId로 처리했었다.
// GraphQL에선 2번째 인자에서 뭉뜽그려서 한번에 처리한다.
// 마찬가지로 msgs를 db.messages로 수정한다.
deleteMessage: (parent, {id, userId}, {db}) => {
const targetIndex = db.messages.findIndex(msg => msg.id === id)
if (targetIndex < 0) throw '메시지가 없습니다.'
if (db.messages[targetIndex].userId !== userId) throw '사용자가 다릅니다.'
db.messages.splice(targetIndex, 1)
return id // res.send(id)
},
}
}
export default messageResolver
// server/src/index.js
import express from 'express'
// graphQL용 서버는 ApolloServer라는걸 이용할겁니다.
import {ApolloServer} from "apollo-server-express";
// import cors from 'cors' // cors 라이브러리 불러온거 삭제해줍니다.
import resolvers from './resolvers/index.js'
import schema from './schema/index.js'
import {readDB} from "./dbController.js";
// ApolloServer를 사용하면 아래 2줄 코드는 필요가 없게됩니다.
// app.use(express.urlencoded({extended: true}))
// app.use(express.json()) // express에서 json 형태로 사용하겠다.
// -------- app.use(cors( ... )) 아래 코드는 필요없으니 삭제해줍니다.
// app.use(cors({
// origin: 'http://localhost:3000', // 클라이언트 서버
// credentials: true,
// }))
// ApolloServer 서버를 생성합니다.
const server = new ApolloServer({
// typeDefs 라는 것이 필요하고, schema(스키마)라는게 필요합니다.
typeDefs: schema,
// 그리고 resolvers이라는 정보가 필요하게됩니다.
resolvers,
// 그리고 resolvers가 참조할 데이터.. 즉, DB 역할을하는 models 부분이 정의가 되어있어야 합니다.
context: {
db: {
// 아래 정의에 대해선 이따 하도록 하겠습니다.
messages: readDB('messages'),
users: readDB('users'),
}
}
});
const app = express(); // <- 이 코드 위치를 applyMiddleware 위쪽인 이 위치로 옮깁니다.
await server.start(); // <- 그리고 server.start() 코드를 await 키워드를 통해 실행하겠습니다.
// 위 정의가 다 되고나면, server.applyMiddleware() 함수를 통해서 아래와 같이 코드를 작성합니다.
server.applyMiddleware({
app,
path: '/graphql',
// 그리고 cors를 이전과 다르게 applyMiddleware의 3번째 인자(옵션값)로 객체형태로 넘겨줍니다.
cors: {
origin: [
'http://localhost:3000',
'https://studio.apollographql.com'
], // 클라이언트 서버
credentials: true,
}
})
// 서버 경로는 8000번
// 그리고 아래도 await 키워드를 붙여주고 첫번째 인자 값을 아래와 같이 수정해줍니다.
await app.listen({port: 8000})
// 위의 await 붙은 코드가 끝나고 아래가 실행될거기 때문에 console.log(...)도 이렇게 밖으로 빼줍니다.
console.log('server listening on 8000...')
// 원래는 아래와 같은 모습이었음.
// app.listen(8000, () => {
// console.log('server listening on 8000...')
// })
그래서 readDB
를 지웁니다.writeDB
만 가지고 가겠습니다.
schema/messages.js
파일에서
messages
message
createMessage
updateMessage
deleteMessage
이렇게 정의했었습니다.
이 내용들 각각에 대해서 실제로 어떻게 동작할지에 대한 정의가 resolvers/messages.js
에서 본격적으로 이뤄집니다.
server/src/index.js
의 context
안에 models
라는 것이 들어있습니다.
이 models
를 resolves/messages.js
파일의 Query
의 각 함수의 3번째 인자로 구조분해할당으로 받아올 수 있습니다.
위에서 본 models
라는 명칭을 db
로 바꿔줍시다.
messages
Query
에서 db.messages
를 return
합니다.
위 내용은 REST API
에서 GET
요청에 대해 정의한 내용과 같습니다.
똑같이 .find()
를 하고, 없는 경우엔 Error
를 띄우고, 있는 경우엔 send
로 응답보내고..
그 내용과 같다고보시면 되겠습니다.
그런데 REST API
의 GET
요청에선 cursor
값을 받았었습니다.GraphQL
에선 일단 cursor
값 없이 모든 데이터를 보여주는 것부터 살펴본 다음에cursor
를 통해 무한 스크롤하는 것을 추가적으로 다뤄보도록 하겠습니다.
messages.js
라우터에서 get messages
, get message
를 지웁니다.
그리고 create
를 보겠습니다.
위 코드를 보시면 아시겠지만, REST API
에서는 어떤걸로 어떻게 넘어오느냐에따라 처리가 달랐습니다.
요청이 /messages:id
이렇게 넘어오면 REST API
서버에서 {params {id}}
로 받고,localhost:3000/?userId=jay
이렇게 넘어오면 {query: {userId}}
이렇게 받았습니다.
또한 post
와 put
요청인 경우는 새로 등록할 또는 업데이트할 정보를 body
로부터 받습니다.post
, put
은 새로 등록할 또는 업데이트할 정보를 2번째 인자로 받아오기 때문… (get
과 delete
는 아님)
하지만 GraphQL은 REST API와 달리 모두 뭉뜽그려서 한군데서 처리한다고 보시면됩니다.
GraphQL GET 정의
GET - 모든 메시지들 불러오는 GET 정의
// server/src/resolvers/messages.js
import {v4} from "uuid";
// readDB는 지우고 writeDB만 가지고 가겠습니다.
import {writeDB} from "../dbController.js";
// setMsgs는 graphQL 서버에서도 사용하므로 살립니다.
const setMsgs = data => writeDB('messages', data)
//-------------------------------------------------------------------
// 그리고 나머지는 유지한 상태에서 messageResolver를 만들겠습니다.
// 이 messageResolver는 Query 명령에 대해서 정의를 하고,
// 또 Mutation 명령에 대해서 정의를 할겁니다.
// 이때 schema에서 정의했던 명령어들, 그 내용을 그대로 쓰면됩니다.
// schema에서 정의했던 명령어들에 대한 본격적인 정의가 여기서 이뤄집니다.
// 아래와 같은 구조로 정의를 해줍니다.
// 각각의 resover에는 parent, args, context라는 세개의 인자가 들어옵니다. 이들 각각에 대해서 graphQL 공식 사이트에선 이렇게 설명을 하고 있습니다.
/**
* parent: 대부분 사용되지 않는 root query type의 이전 객체, parent 객체라고 하고, 거의 사용되지 않습니다.
* args: graphQL Query에 필요한 필드에 제공되는 인수(parameter)가 들어있는 객체
* context: 로그인한 사용자 정보, DB Access 권한 등의 중요한 정보들을 담고있는 객체
* 위 각각에 어떤 내용이 들어가있는지는 나중에 따로 살펴보도록 하겠습니다.
* */
const messageResolver = {
Query: {
// 일단은 아래 messages의 인자들은 messages를 가져오는 '정보'니깐, 필요한 부분은 3번째 인자인 context입니다.
// context 안에 models라는 것이 들어있는데, 이 models는 아까 index.js에서 정의한.. 저희가 직접 정의한 것입니다.
messages: (parent, args, {db}) => {
// console.log({obj, args, context}) // 각 인자에 어떤 내용이 들어가있는지는 나중에 따로 살펴보도록 하겠습니다.
// 아래와 같이 db.messages라고 정의하고 일단은 넘어가도록 하겠습니다.
return db.messages
},
// message를 하나 불러오는 Query입니다.
// 2번째 인자에서 Query에 필요한 parameter 값이 온다고 했죠? 여기에 id 값이 들어오게 될겁니다.
// id가 없을 경우를 대비해 default 값을 넣어줬습니다.
// 그리고 마지막 3번째 인자로 마찬가지로 db가 들어올겁니다.
message: (parent, {id = ''}, {db}) => {
// 그럼 db.messages에서 id가 2번째 인자로 넘어온 id와 일치하는 것을 찾아주면 될겁니다.
return db.messages.find(msg => msg.id === id)
},
},
Mutation: {
// 2번째 데이터, param 데이터들..
// REST API의 create 메소드를 보면, 저희에게 필요했던 데이터가 body에 있는 text와 userId였죠?
// 그냥 body에서 왔는지, params에서 왔는지, query에서 왔는지 신경쓸거 없이 아래처럼 2번째 인자에 text, userId를 다 받아오면됩니다.
// graphQL은 모두 뭉뜽그려서 한군데서 처리하면된다 라고 생각하시면 됩니다.
createMessage: (parent, {text, userId}, {db}) => {
// 그러면 create에 있는 내용은 사실상 REST API에 있는거랑 똑같은거죠.
// const msgs = getMsgs(); // getMsgs는 db에있는 messages를 사용하면되니깐 필요가 없고
const newMsg = {
id: v4(),
text, // 여기는 body.text 이렇게 올 필요가 없으므로 왼쪽과 같이 수정하면 됩니다.
userId, // 여기도 body 필요없으므로 마찬가지로..
timestamp: Date.now(),
}
db.messages.unshift(newMsg) // msgs.unshift(newMsg) <- 이 코드 대신 왼쪽 코드로 작성해주고
setMsgs(db.messages) // setMsgs(msgs); <- 이걸 왼쪽과 같이 수정
return newMsg // res.send(newMsg) <- 이걸 return newMsg로 수정
},
updateMessage: () => {},
deleteMessage: () => {},
}
}
const getMsgs = () => readDB('messages')
const messagesRoute = [
{ // GET MESSAGES : 전체 메시지를 가져오는 명령
method: 'get',
route: '/messages',
handler: ({query: {cursor = ''}}, res) => {
const msgs = getMsgs();
const fromIndex = msgs.findIndex(msg => msg.id === cursor) + 1
res.send(msgs.slice(fromIndex, fromIndex + 15))
}
},
{ // GET MESSAGE : id 하나에 대한 메시지를 가져오는 것도 살펴봅시다.
method: 'get',
route: '/messages/:id',
handler: ({params: {id}, res}) => {
try {
const msgs = getMsgs();
const msg = msgs.find(m => m.id === id)
if (!msg) throw Error('not found')
res.send(msg)
} catch (err) {
res.status(404).send({ error: err })
}
}
},
{ // CREATE MESSAGE
method: 'post',
route: '/messages',
handler: ({body}, res) => {
try {
if (!body.userId) throw Error('no userId');
const msgs = getMsgs();
const newMsg = {
id: v4(),
text: body.text,
userId: body.userId,
timestamp: Date.now(),
}
msgs.unshift(newMsg)
setMsgs(msgs);
res.send(newMsg)
} catch (err) {
res.status(500).send({error: err})
}
}
},
{ // UPDATE MESSAGE
method: 'put',
route: '/messages/:id',
handler: ({body, params: {id}}, res) => {
try {
const msgs = getMsgs();
const targetIndex = msgs.findIndex(msg => msg.id === id)
if (targetIndex < 0) throw '메시지가 없습니다.'
if (msgs[targetIndex].userId !== body.userId) throw '사용자가 다릅니다.'
const newMsg = {
...msgs[targetIndex],
text: body.text,
}
msgs.splice(targetIndex, 1, newMsg)
setMsgs(msgs)
res.send(newMsg)
} catch (err) {
res.status(500).send({error: err})
}
}
},
{ // DELETE MESSAGE
method: 'delete',
route: '/messages/:id',
handler: ({params: {id}, query: {userId}}, res) => {
try {
const msgs = getMsgs();
const targetIndex = msgs.findIndex(msg => msg.id === id)
if (targetIndex < 0) throw '메시지가 없습니다.'
if (msgs[targetIndex].userId !== userId) throw '사용자가 다릅니다.'
msgs.splice(targetIndex, 1)
res.send(id)
} catch (err) {
res.status(500).send({error: err})
}
}
},
]
export default messagesRoute
GET - 특정 메시지 하나만 불러오는 GET 정의
// server/src/resolvers/messages.js
import {v4} from "uuid";
// readDB는 지우고 writeDB만 가지고 가겠습니다.
import {writeDB} from "../dbController.js";
// setMsgs는 graphQL 서버에서도 사용하므로 살립니다.
const setMsgs = data => writeDB('messages', data)
//-------------------------------------------------------------------
// 그리고 나머지는 유지한 상태에서 messageResolver를 만들겠습니다.
// 이 messageResolver는 Query 명령에 대해서 정의를 하고,
// 또 Mutation 명령에 대해서 정의를 할겁니다.
// 이때 schema에서 정의했던 명령어들, 그 내용을 그대로 쓰면됩니다.
// schema에서 정의했던 명령어들에 대한 본격적인 정의가 여기서 이뤄집니다.
// 아래와 같은 구조로 정의를 해줍니다.
// 각각의 resover에는 parent, args, context라는 세개의 인자가 들어옵니다. 이들 각각에 대해서 graphQL 공식 사이트에선 이렇게 설명을 하고 있습니다.
/**
* parent: 대부분 사용되지 않는 root query type의 이전 객체, parent 객체라고 하고, 거의 사용되지 않습니다.
* args: graphQL Query에 필요한 필드에 제공되는 인수(parameter)가 들어있는 객체
* context: 로그인한 사용자 정보, DB Access 권한 등의 중요한 정보들을 담고있는 객체
* 위 각각에 어떤 내용이 들어가있는지는 나중에 따로 살펴보도록 하겠습니다.
* */
const messageResolver = {
Query: {
// 일단은 아래 messages의 인자들은 messages를 가져오는 '정보'니깐, 필요한 부분은 3번째 인자인 context입니다.
// context 안에 models라는 것이 들어있는데, 이 models는 아까 index.js에서 정의한.. 저희가 직접 정의한 것입니다.
messages: (parent, args, {db}) => {
// console.log({obj, args, context}) // 각 인자에 어떤 내용이 들어가있는지는 나중에 따로 살펴보도록 하겠습니다.
// 아래와 같이 db.messages라고 정의하고 일단은 넘어가도록 하겠습니다.
return db.messages
},
// message를 하나 불러오는 Query입니다.
// 2번째 인자에서 Query에 필요한 parameter 값이 온다고 했죠? 여기에 id 값이 들어오게 될겁니다.
// id가 없을 경우를 대비해 default 값을 넣어줬습니다.
// 그리고 마지막 3번째 인자로 마찬가지로 db가 들어올겁니다.
message: (parent, {id = ''}, {db}) => {
// 그럼 db.messages에서 id가 2번째 인자로 넘어온 id와 일치하는 것을 찾아주면 될겁니다.
return db.messages.find(msg => msg.id === id)
},
},
Mutation: {
// 2번째 데이터, param 데이터들..
// REST API의 create 메소드를 보면, 저희에게 필요했던 데이터가 body에 있는 text와 userId였죠?
// 그냥 body에서 왔는지, params에서 왔는지, query에서 왔는지 신경쓸거 없이 아래처럼 2번째 인자에 text, userId를 다 받아오면됩니다.
// graphQL은 모두 뭉뜽그려서 한군데서 처리하면된다 라고 생각하시면 됩니다.
createMessage: (parent, {text, userId}, {db}) => {
// 그러면 create에 있는 내용은 사실상 REST API에 있는거랑 똑같은거죠.
// const msgs = getMsgs(); // getMsgs는 db에있는 messages를 사용하면되니깐 필요가 없고
const newMsg = {
id: v4(),
text, // 여기는 body.text 이렇게 올 필요가 없으므로 왼쪽과 같이 수정하면 됩니다.
userId, // 여기도 body 필요없으므로 마찬가지로..
timestamp: Date.now(),
}
db.messages.unshift(newMsg) // msgs.unshift(newMsg) <- 이 코드 대신 왼쪽 코드로 작성해주고
setMsgs(db.messages) // setMsgs(msgs); <- 이걸 왼쪽과 같이 수정
return newMsg // res.send(newMsg) <- 이걸 return newMsg로 수정
},
updateMessage: () => {},
deleteMessage: () => {},
}
}
const getMsgs = () => readDB('messages')
const messagesRoute = [
{ // GET MESSAGES : 전체 메시지를 가져오는 명령
method: 'get',
route: '/messages',
handler: ({query: {cursor = ''}}, res) => {
const msgs = getMsgs();
const fromIndex = msgs.findIndex(msg => msg.id === cursor) + 1
res.send(msgs.slice(fromIndex, fromIndex + 15))
}
},
{ // GET MESSAGE : id 하나에 대한 메시지를 가져오는 것도 살펴봅시다.
method: 'get',
route: '/messages/:id',
handler: ({params: {id}, res}) => {
try {
const msgs = getMsgs();
const msg = msgs.find(m => m.id === id)
if (!msg) throw Error('not found')
res.send(msg)
} catch (err) {
res.status(404).send({ error: err })
}
}
},
{ // CREATE MESSAGE
method: 'post',
route: '/messages',
handler: ({body}, res) => {
try {
if (!body.userId) throw Error('no userId');
const msgs = getMsgs();
const newMsg = {
id: v4(),
text: body.text,
userId: body.userId,
timestamp: Date.now(),
}
msgs.unshift(newMsg)
setMsgs(msgs);
res.send(newMsg)
} catch (err) {
res.status(500).send({error: err})
}
}
},
{ // UPDATE MESSAGE
method: 'put',
route: '/messages/:id',
handler: ({body, params: {id}}, res) => {
try {
const msgs = getMsgs();
const targetIndex = msgs.findIndex(msg => msg.id === id)
if (targetIndex < 0) throw '메시지가 없습니다.'
if (msgs[targetIndex].userId !== body.userId) throw '사용자가 다릅니다.'
const newMsg = {
...msgs[targetIndex],
text: body.text,
}
msgs.splice(targetIndex, 1, newMsg)
setMsgs(msgs)
res.send(newMsg)
} catch (err) {
res.status(500).send({error: err})
}
}
},
{ // DELETE MESSAGE
method: 'delete',
route: '/messages/:id',
handler: ({params: {id}, query: {userId}}, res) => {
try {
const msgs = getMsgs();
const targetIndex = msgs.findIndex(msg => msg.id === id)
if (targetIndex < 0) throw '메시지가 없습니다.'
if (msgs[targetIndex].userId !== userId) throw '사용자가 다릅니다.'
msgs.splice(targetIndex, 1)
res.send(id)
} catch (err) {
res.status(500).send({error: err})
}
}
},
]
export default messagesRoute
POST - create 정의
// server/src/resolvers/messages.js
import {v4} from "uuid";
// readDB는 지우고 writeDB만 가지고 가겠습니다.
import {writeDB} from "../dbController.js";
// setMsgs는 graphQL 서버에서도 사용하므로 살립니다.
const setMsgs = data => writeDB('messages', data)
//-------------------------------------------------------------------
// 그리고 나머지는 유지한 상태에서 messageResolver를 만들겠습니다.
// 이 messageResolver는 Query 명령에 대해서 정의를 하고,
// 또 Mutation 명령에 대해서 정의를 할겁니다.
// 이때 schema에서 정의했던 명령어들, 그 내용을 그대로 쓰면됩니다.
// schema에서 정의했던 명령어들에 대한 본격적인 정의가 여기서 이뤄집니다.
// 아래와 같은 구조로 정의를 해줍니다.
// 각각의 resover에는 parent, args, context라는 세개의 인자가 들어옵니다. 이들 각각에 대해서 graphQL 공식 사이트에선 이렇게 설명을 하고 있습니다.
/**
* parent: 대부분 사용되지 않는 root query type의 이전 객체, parent 객체라고 하고, 거의 사용되지 않습니다.
* args: graphQL Query에 필요한 필드에 제공되는 인수(parameter)가 들어있는 객체
* context: 로그인한 사용자 정보, DB Access 권한 등의 중요한 정보들을 담고있는 객체
* 위 각각에 어떤 내용이 들어가있는지는 나중에 따로 살펴보도록 하겠습니다.
* */
const messageResolver = {
Query: {
// 일단은 아래 messages의 인자들은 messages를 가져오는 '정보'니깐, 필요한 부분은 3번째 인자인 context입니다.
// context 안에 models라는 것이 들어있는데, 이 models는 아까 index.js에서 정의한.. 저희가 직접 정의한 것입니다.
messages: (parent, args, {db}) => {
// console.log({obj, args, context}) // 각 인자에 어떤 내용이 들어가있는지는 나중에 따로 살펴보도록 하겠습니다.
// 아래와 같이 db.messages라고 정의하고 일단은 넘어가도록 하겠습니다.
return db.messages
},
// message를 하나 불러오는 Query입니다.
// 2번째 인자에서 Query에 필요한 parameter 값이 온다고 했죠? 여기에 id 값이 들어오게 될겁니다.
// id가 없을 경우를 대비해 default 값을 넣어줬습니다.
// 그리고 마지막 3번째 인자로 마찬가지로 db가 들어올겁니다.
message: (parent, {id = ''}, {db}) => {
// 그럼 db.messages에서 id가 2번째 인자로 넘어온 id와 일치하는 것을 찾아주면 될겁니다.
return db.messages.find(msg => msg.id === id)
},
},
Mutation: {
// 2번째 데이터, param 데이터들..
// REST API의 create 메소드를 보면, 저희에게 필요했던 데이터가 body에 있는 text와 userId였죠?
// 그냥 body에서 왔는지, params에서 왔는지, query에서 왔는지 신경쓸거 없이 아래처럼 2번째 인자에 text, userId를 다 받아오면됩니다.
// graphQL은 모두 뭉뜽그려서 한군데서 처리하면된다 라고 생각하시면 됩니다.
createMessage: (parent, {text, userId}, {db}) => {
// 그러면 create에 있는 내용은 사실상 REST API에 있는거랑 똑같은거죠.
// const msgs = getMsgs(); // getMsgs는 db에있는 messages를 사용하면되니깐 필요가 없고
const newMsg = {
id: v4(),
text, // 여기는 body.text 이렇게 올 필요가 없으므로 왼쪽과 같이 수정하면 됩니다.
userId, // 여기도 body 필요없으므로 마찬가지로..
timestamp: Date.now(),
}
db.messages.unshift(newMsg) // msgs.unshift(newMsg) <- 이 코드 대신 왼쪽 코드로 작성해주고
setMsgs(db.messages) // setMsgs(msgs); <- 이걸 왼쪽과 같이 수정
return newMsg // res.send(newMsg) <- 이걸 return newMsg로 수정
},
updateMessage: () => {},
deleteMessage: () => {},
}
}
const getMsgs = () => readDB('messages')
const messagesRoute = [
{ // GET MESSAGES : 전체 메시지를 가져오는 명령
method: 'get',
route: '/messages',
handler: ({query: {cursor = ''}}, res) => {
const msgs = getMsgs();
const fromIndex = msgs.findIndex(msg => msg.id === cursor) + 1
res.send(msgs.slice(fromIndex, fromIndex + 15))
}
},
{ // GET MESSAGE : id 하나에 대한 메시지를 가져오는 것도 살펴봅시다.
method: 'get',
route: '/messages/:id',
handler: ({params: {id}, res}) => {
try {
const msgs = getMsgs();
const msg = msgs.find(m => m.id === id)
if (!msg) throw Error('not found')
res.send(msg)
} catch (err) {
res.status(404).send({ error: err })
}
}
},
{ // CREATE MESSAGE
method: 'post',
route: '/messages',
handler: ({body}, res) => {
try {
if (!body.userId) throw Error('no userId');
const msgs = getMsgs();
const newMsg = {
id: v4(),
text: body.text,
userId: body.userId,
timestamp: Date.now(),
}
msgs.unshift(newMsg)
setMsgs(msgs);
res.send(newMsg)
} catch (err) {
res.status(500).send({error: err})
}
}
},
{ // UPDATE MESSAGE
method: 'put',
route: '/messages/:id',
handler: ({body, params: {id}}, res) => {
try {
const msgs = getMsgs();
const targetIndex = msgs.findIndex(msg => msg.id === id)
if (targetIndex < 0) throw '메시지가 없습니다.'
if (msgs[targetIndex].userId !== body.userId) throw '사용자가 다릅니다.'
const newMsg = {
...msgs[targetIndex],
text: body.text,
}
msgs.splice(targetIndex, 1, newMsg)
setMsgs(msgs)
res.send(newMsg)
} catch (err) {
res.status(500).send({error: err})
}
}
},
{ // DELETE MESSAGE
method: 'delete',
route: '/messages/:id',
handler: ({params: {id}, query: {userId}}, res) => {
try {
const msgs = getMsgs();
const targetIndex = msgs.findIndex(msg => msg.id === id)
if (targetIndex < 0) throw '메시지가 없습니다.'
if (msgs[targetIndex].userId !== userId) throw '사용자가 다릅니다.'
msgs.splice(targetIndex, 1)
res.send(id)
} catch (err) {
res.status(500).send({error: err})
}
}
},
]
export default messagesRoute
PUT - update
/message/:id
이런식으로 들어오는 것이 params
post(create)
, put(update)
에선 text
, userId
는 body
로 들어온다.REST API
에서 post
, put
메소드는 두번째 인자가 data
를 전달하는 인자이기 때문에.get
, delete
는 2번째인자가 config(옵션)
설정하는 인자이다. data
를 받는 인자가 없다.
// server/src/resolvers/messages.js
import {v4} from "uuid";
// readDB는 지우고 writeDB만 가지고 가겠습니다.
import {writeDB} from "../dbController.js";
// setMsgs는 graphQL 서버에서도 사용하므로 살립니다.
const setMsgs = data => writeDB('messages', data)
//-------------------------------------------------------------------
// 그리고 나머지는 유지한 상태에서 messageResolver를 만들겠습니다.
// 이 messageResolver는 Query 명령에 대해서 정의를 하고,
// 또 Mutation 명령에 대해서 정의를 할겁니다.
// 이때 schema에서 정의했던 명령어들, 그 내용을 그대로 쓰면됩니다.
// schema에서 정의했던 명령어들에 대한 본격적인 정의가 여기서 이뤄집니다.
// 아래와 같은 구조로 정의를 해줍니다.
// 각각의 resover에는 parent, args, context라는 세개의 인자가 들어옵니다. 이들 각각에 대해서 graphQL 공식 사이트에선 이렇게 설명을 하고 있습니다.
/**
* parent: 대부분 사용되지 않는 root query type의 이전 객체, parent 객체라고 하고, 거의 사용되지 않습니다.
* args: graphQL Query에 필요한 필드에 제공되는 인수(parameter)가 들어있는 객체
* context: 로그인한 사용자 정보, DB Access 권한 등의 중요한 정보들을 담고있는 객체
* 위 각각에 어떤 내용이 들어가있는지는 나중에 따로 살펴보도록 하겠습니다.
* */
const messageResolver = {
Query: {
// 일단은 아래 messages의 인자들은 messages를 가져오는 '정보'니깐, 필요한 부분은 3번째 인자인 context입니다.
// context 안에 models라는 것이 들어있는데, 이 models는 아까 index.js에서 정의한.. 저희가 직접 정의한 것입니다.
messages: (parent, args, {db}) => {
// console.log({obj, args, context}) // 각 인자에 어떤 내용이 들어가있는지는 나중에 따로 살펴보도록 하겠습니다.
// 아래와 같이 db.messages라고 정의하고 일단은 넘어가도록 하겠습니다.
return db.messages
},
// message를 하나 불러오는 Query입니다.
// 2번째 인자에서 Query에 필요한 parameter 값이 온다고 했죠? 여기에 id 값이 들어오게 될겁니다.
// id가 없을 경우를 대비해 default 값을 넣어줬습니다.
// 그리고 마지막 3번째 인자로 마찬가지로 db가 들어올겁니다.
message: (parent, {id = ''}, {db}) => {
// 그럼 db.messages에서 id가 2번째 인자로 넘어온 id와 일치하는 것을 찾아주면 될겁니다.
return db.messages.find(msg => msg.id === id)
},
},
Mutation: {
// 2번째 데이터, param 데이터들..
// REST API의 create 메소드를 보면, 저희에게 필요했던 데이터가 body에 있는 text와 userId였죠?
// 그냥 body에서 왔는지, params에서 왔는지, query에서 왔는지 신경쓸거 없이 아래처럼 2번째 인자에 text, userId를 다 받아오면됩니다.
// graphQL은 모두 뭉뜽그려서 한군데서 처리하면된다 라고 생각하시면 됩니다.
createMessage: (parent, {text, userId}, {db}) => {
// 그러면 create에 있는 내용은 사실상 REST API에 있는거랑 똑같은거죠.
// const msgs = getMsgs(); // getMsgs는 db에있는 messages를 사용하면되니깐 필요가 없고
const newMsg = {
id: v4(),
text, // 여기는 body.text 이렇게 올 필요가 없으므로 왼쪽과 같이 수정하면 됩니다.
userId, // 여기도 body 필요없으므로 마찬가지로..
timestamp: Date.now(),
}
db.messages.unshift(newMsg) // msgs.unshift(newMsg) <- 이 코드 대신 왼쪽 코드로 작성해주고
setMsgs(db.messages) // setMsgs(msgs); <- 이걸 왼쪽과 같이 수정
return newMsg // res.send(newMsg) <- 이걸 return newMsg로 수정
},
// update에선 params에 들어오는 id가 필요했었다. body.text, body.userId(roy, jay)도 필요했었다.
// 2번째 인자에서 이 3개를 뭉뜽그려서 꺼내옵니다.
// 그리고 3번재 인자에선 마찬가지로 db를 꺼내옵니다.
updateMessage: (parent, {id, text, userId}, {db}) => {
// REST API의 update 내용을 가져옵니다.
// msgs는 db.messages로 수정합니다.
const targetIndex = db.messages.findIndex(msg => msg.id === id)
if (targetIndex < 0) throw Error('메시지가 없습니다.') // 일단 try catch 구문을 사용안해서, Error 객체를 통한 에러처리는 여기서 보내겠습니다.
if (db.messages[targetIndex].userId !== userId) throw Error('사용자가 다릅니다.') // 일단 try catch 구문을 사용안해서, Error 객체를 통한 에러처리는 여기서 보내겠습니다.
const newMsg = {
...db.messages[targetIndex],
text,
}
db.messages.splice(targetIndex, 1, newMsg)
setMsgs(db.messages)
return newMsg; // res.send(newMsg) 이게아니라 그냥 return을 해주면됩니다.
},
deleteMessage: () => {},
}
}
const getMsgs = () => readDB('messages')
const messagesRoute = [
{ // UPDATE MESSAGE
method: 'put',
route: '/messages/:id',
handler: ({body, params: {id}}, res) => {
try {
const msgs = getMsgs();
const targetIndex = msgs.findIndex(msg => msg.id === id)
if (targetIndex < 0) throw '메시지가 없습니다.'
if (msgs[targetIndex].userId !== body.userId) throw '사용자가 다릅니다.'
const newMsg = {
...msgs[targetIndex],
text: body.text,
}
msgs.splice(targetIndex, 1, newMsg)
setMsgs(msgs)
res.send(newMsg)
} catch (err) {
res.status(500).send({error: err})
}
}
},
{ // DELETE MESSAGE
method: 'delete',
route: '/messages/:id',
handler: ({params: {id}, query: {userId}}, res) => {
try {
const msgs = getMsgs();
const targetIndex = msgs.findIndex(msg => msg.id === id)
if (targetIndex < 0) throw '메시지가 없습니다.'
if (msgs[targetIndex].userId !== userId) throw '사용자가 다릅니다.'
msgs.splice(targetIndex, 1)
res.send(id)
} catch (err) {
res.status(500).send({error: err})
}
}
},
]
export default messagesRoute
DELETE
그래서 delete
처리할 때 body
에서 못받아오고 위와 같이 {query: {userId}}
로 받아왔지만,GraphQL
에서 뭉뜽그려서 한번에 받아올 수 있습니다.
// server/src/resolvers/messages.js
import {v4} from "uuid";
// readDB는 지우고 writeDB만 가지고 가겠습니다.
import {writeDB} from "../dbController.js";
// setMsgs는 graphQL 서버에서도 사용하므로 살립니다.
const setMsgs = data => writeDB('messages', data)
//-------------------------------------------------------------------
// 그리고 나머지는 유지한 상태에서 messageResolver를 만들겠습니다.
// 이 messageResolver는 Query 명령에 대해서 정의를 하고,
// 또 Mutation 명령에 대해서 정의를 할겁니다.
// 이때 schema에서 정의했던 명령어들, 그 내용을 그대로 쓰면됩니다.
// schema에서 정의했던 명령어들에 대한 본격적인 정의가 여기서 이뤄집니다.
// 아래와 같은 구조로 정의를 해줍니다.
// 각각의 resover에는 parent, args, context라는 세개의 인자가 들어옵니다. 이들 각각에 대해서 graphQL 공식 사이트에선 이렇게 설명을 하고 있습니다.
/**
* parent: 대부분 사용되지 않는 root query type의 이전 객체, parent 객체라고 하고, 거의 사용되지 않습니다.
* args: graphQL Query에 필요한 필드에 제공되는 인수(parameter)가 들어있는 객체
* context: 로그인한 사용자 정보, DB Access 권한 등의 중요한 정보들을 담고있는 객체
* 위 각각에 어떤 내용이 들어가있는지는 나중에 따로 살펴보도록 하겠습니다.
* */
const messageResolver = {
Query: {
// 일단은 아래 messages의 인자들은 messages를 가져오는 '정보'니깐, 필요한 부분은 3번째 인자인 context입니다.
// context 안에 models라는 것이 들어있는데, 이 models는 아까 index.js에서 정의한.. 저희가 직접 정의한 것입니다.
messages: (parent, args, {db}) => {
// console.log({obj, args, context}) // 각 인자에 어떤 내용이 들어가있는지는 나중에 따로 살펴보도록 하겠습니다.
// 아래와 같이 db.messages라고 정의하고 일단은 넘어가도록 하겠습니다.
return db.messages
},
// message를 하나 불러오는 Query입니다.
// 2번째 인자에서 Query에 필요한 parameter 값이 온다고 했죠? 여기에 id 값이 들어오게 될겁니다.
// id가 없을 경우를 대비해 default 값을 넣어줬습니다.
// 그리고 마지막 3번째 인자로 마찬가지로 db가 들어올겁니다.
message: (parent, {id = ''}, {db}) => {
// 그럼 db.messages에서 id가 2번째 인자로 넘어온 id와 일치하는 것을 찾아주면 될겁니다.
return db.messages.find(msg => msg.id === id)
},
},
Mutation: {
// 2번째 데이터, param 데이터들..
// REST API의 create 메소드를 보면, 저희에게 필요했던 데이터가 body에 있는 text와 userId였죠?
// 그냥 body에서 왔는지, params에서 왔는지, query에서 왔는지 신경쓸거 없이 아래처럼 2번째 인자에 text, userId를 다 받아오면됩니다.
// graphQL은 모두 뭉뜽그려서 한군데서 처리하면된다 라고 생각하시면 됩니다.
createMessage: (parent, {text, userId}, {db}) => {
// 그러면 create에 있는 내용은 사실상 REST API에 있는거랑 똑같은거죠.
// const msgs = getMsgs(); // getMsgs는 db에있는 messages를 사용하면되니깐 필요가 없고
const newMsg = {
id: v4(),
text, // 여기는 body.text 이렇게 올 필요가 없으므로 왼쪽과 같이 수정하면 됩니다.
userId, // 여기도 body 필요없으므로 마찬가지로..
timestamp: Date.now(),
}
db.messages.unshift(newMsg) // msgs.unshift(newMsg) <- 이 코드 대신 왼쪽 코드로 작성해주고
setMsgs(db.messages) // setMsgs(msgs); <- 이걸 왼쪽과 같이 수정
return newMsg // res.send(newMsg) <- 이걸 return newMsg로 수정
},
// update에선 params에 들어오는 id가 필요했었다. body.text, body.userId(roy, jay)도 필요했었다.
// 2번째 인자에서 이 3개를 뭉뜽그려서 꺼내옵니다.
// 그리고 3번재 인자에선 마찬가지로 db를 꺼내옵니다.
updateMessage: (parent, {id, text, userId}, {db}) => {
// REST API의 update 내용을 가져옵니다.
// msgs는 db.messages로 수정합니다.
const targetIndex = db.messages.findIndex(msg => msg.id === id)
if (targetIndex < 0) throw Error('메시지가 없습니다.') // 일단 try catch 구문을 사용안해서, Error 객체를 통한 에러처리는 여기서 보내겠습니다.
if (db.messages[targetIndex].userId !== userId) throw Error('사용자가 다릅니다.') // 일단 try catch 구문을 사용안해서, Error 객체를 통한 에러처리는 여기서 보내겠습니다.
const newMsg = {
...db.messages[targetIndex],
text,
}
db.messages.splice(targetIndex, 1, newMsg)
setMsgs(db.messages)
return newMsg; // res.send(newMsg) 이게아니라 그냥 return을 해주면됩니다.
},
// delete도 REST API에선 params의 id와 queryString의 userId로 처리했었다.
// GraphQL에선 2번째 인자에서 뭉뜽그려서 한번에 처리한다.
// 마찬가지로 msgs를 db.messages로 수정한다.
deleteMessage: (parent, {id, userId}, {db}) => {
const targetIndex = db.messages.findIndex(msg => msg.id === id)
if (targetIndex < 0) throw '메시지가 없습니다.'
if (db.messages[targetIndex].userId !== userId) throw '사용자가 다릅니다.'
db.messages.splice(targetIndex, 1)
return id // res.send(id)
},
}
}
export default messageResolver
GraphQL
에서 messages.js
관련 작업은 모두 끝났습니다.
이제 users.js
쪽을 해보겠습니다.
users.js resolver 정의 - GET
이번엔 users.js
파일을 통해 user
의 resolvers(REST API의 routes)
를 처리해봅시다.
모든 users
정보를 불러오는 Query
는 위와 같이 만듭니다.
users: (parent, args, {db}) => db.users
그런데 schema
만들 때 말씀드렸듯이 users
는 배열로 받기로 정의했습니다. (GraphQL
엔 객체형태가 없고 배열형태밖에 없기 때문..)
즉 위와 같이 {}
객체로 되어있는 db
정보를 배열로 만들어줘야한다.
배열로 만들기 위해, 굳이 "roy"
, "jay"
이런 key
들을 살릴 필요는 없을 거 같습니다.
뒤의 프로퍼티 값만 살려서 배열로 반환하면 될 거 같습니다.
// server/src/resolvers/users.js
const userResolver = {
// userResolver는 Query만 있을겁니다.
// 일단 GET 요청 - '/graphql'로 들어가서 나눠지는건데, REST API에서도 user는 GET요청만 만들어놨었다.
// graphQL에서의 GET 요청은 Query
Query: {
// 모든 user 정보를 다 불러오는 Query입니다.
// parent와 args는 사용할일이 없고 db만 쓰겠죠?
// db에있는 users를 전부 반환해주면됩니다.
// 그런데 schema에서 말씀드렸듯이 users는 배열로 받기로 되어있었습니다.
// 그래서 아래와 같이 Object.values() 함수를 사용해 value만 배열 형태로 반환하도록하면 될 거 같습니다.
users: (parent, args, {db}) => Object.values(db.users),
user: (parent, {id}, {db}) => db.users[id],
}
}
export default userResolver
resolvers 들을 한데 묶어주는 index.js
resolvers(messages.js, users.js)들을 한데묶는 index.js
파일을 생성합니다.
// server/src/resolvers/index.js
import messageResolver from "./messages";
import userResolver from "./users";
export default [messageResolver, userResolver]
다 만들었습니다.
server/src/index.js
이제 server/src/index.js
에서 resolvers
와 schema
를 각각 불러옵니다.
그리고 위와 같이 server/src/index.js
에서 import {readDB} from "./dbConroller";
을 통해서 readDB
도 불러오겠습니다.
커스텀 파일들은 .js
도 꼭 명시해주십시오.
명시 안해도 되는 경우도 있는 것 같지만.. 꼭 명시를 해야 불필요한 오류를 줄일 수 있습니다.
이렇게 resolvers
나 schema
정의는 끝났고, db
쪽만 정의를 하면 됩니다.
위와 같이 context
부분에 messages
와 users
부분에 readDB
를 작성해주어 DB
를 불러오게합니다.
이렇게하면 GraphQL
서버 완성입니다.
실행을 해보도록 하겠습니다.
서버만 우선 띄워보겠습니다.
yarn run server
yarn run v1.22.10
$ yarn workspace server start
$ nodemon ./src/index.js
[nodemon] 2.0.12
[nodemon] to restart at any time, enter `rs`
[nodemon] watching path(s): src\**\*
[nodemon] watching extensions: js,mjs,json
[nodemon] starting `node ./src/index.js`
internal/process/esm_loader.js:74
internalBinding('errors').triggerUncaughtException(
^
Error [ERR_MODULE_NOT_FOUND]: Cannot find module 'D:\react-training-graphql\server\src\resolvers\messages' imported from D:\react-training-graphql\server\src\resolvers\index.js
at finalizeResolution (internal/modules/esm/resolve.js:276:11)
at moduleResolve (internal/modules/esm/resolve.js:699:10)
at Loader.defaultResolve [as _resolve] (internal/modules/esm/resolve.js:810:11)
at Loader.resolve (internal/modules/esm/loader.js:88:40)
at Loader.getModuleJob (internal/modules/esm/loader.js:241:28)
at ModuleWrap.<anonymous> (internal/modules/esm/module_job.js:56:40)
at link (internal/modules/esm/module_job.js:55:36) {
code: 'ERR_MODULE_NOT_FOUND'
}
[nodemon] app crashed - waiting for file changes before starting...
에러가납니다.
커스텀 파일은 꼭 .js
를 붙여주도록합시다.
yarn run server
yarn run v1.22.10
$ yarn workspace server start
$ nodemon ./src/index.js
[nodemon] 2.0.12
[nodemon] to restart at any time, enter `rs`
[nodemon] watching path(s): src\**\*
[nodemon] watching extensions: js,mjs,json
[nodemon] starting `node ./src/index.js`
D:\react-training-graphql\server
D:\react-training-graphql\node_modules\graphql\error\syntaxError.js:15
return new _GraphQLError.GraphQLError("Syntax Error: ".concat(description), undefined, source, [position]);
^
GraphQLError [Object]: Syntax Error: Expected Name, found ")".
at syntaxError (D:\react-training-graphql\node_modules\graphql\error\syntaxError.js:15:10)
at Parser.expectToken (D:\react-training-graphql\node_modules\graphql\language\parser.js:1413:40)
at Parser.parseName (D:\react-training-graphql\node_modules\graphql\language\parser.js:98:22)
at Parser.parseInputValueDef (D:\react-training-graphql\node_modules\graphql\language\parser.js:911:21)
at Parser.optionalMany (D:\react-training-graphql\node_modules\graphql\language\parser.js:1503:28)
at Parser.parseArgumentDefs (D:\react-training-graphql\node_modules\graphql\language\parser.js:900:17)
at Parser.parseFieldDefinition (D:\react-training-graphql\node_modules\graphql\language\parser.js:880:21)
at Parser.optionalMany (D:\react-training-graphql\node_modules\graphql\language\parser.js:1503:28)
at Parser.parseFieldsDefinition (D:\react-training-graphql\node_modules\graphql\language\parser.js:868:17)
at Parser.parseObjectTypeExtension (D:\react-training-graphql\node_modules\graphql\language\parser.js:1176:23) {
locations: [ { line: 8, column: 15 } ]
}
[nodemon] app crashed - waiting for file changes before starting...
이번엔 Syntax Error
가 납니다.
정확히 어디서 에러가난 것인지 명시를 안해주고 있습니다.
일단 이럴 경우엔 schema
에서 난 에러일 확률이 크거든요.
원래는 users(): [Users!]!
라고 되어있었는데 여기서 소괄호를 지워줍니다.
괄호 안에 아무것도 오지 않을 경우엔 괄호를 지워줍니다.
messages(cursor: ID): [Message!]!
여기서도 cursor: ID
에 올게 없으므로 지워줍니다.messages: [Message!]!
아직 에러가 해결 안된 상태.
yarn run server
yarn run v1.22.10
$ yarn workspace server start
$ nodemon ./src/index.js
[nodemon] 2.0.12
[nodemon] to restart at any time, enter `rs`
[nodemon] watching path(s): src\**\*
[nodemon] watching extensions: js,mjs,json
[nodemon] starting `node ./src/index.js`
D:\react-training-graphql\server
D:\react-training-graphql\node_modules\graphql\validation\validate.js:107
throw new Error(errors.map(function (error) {
^
Error: Unknown type "User". Did you mean "user"?
Unknown type "User". Did you mean "user"?
Unknown type "User". Did you mean "user"?
at assertValidSDL (D:\react-training-graphql\node_modules\graphql\validation\validate.js:107:11)
at Object.buildASTSchema (D:\react-training-graphql\node_modules\graphql\utilities\buildASTSchema.js:45:34)
at Object.makeExecutableSchema (D:\react-training-graphql\node_modules\@graphql-tools\schema\index.js:497:26)
at ApolloServer.constructSchema (D:\react-training-graphql\node_modules\apollo-server-core\dist\ApolloServer.js:314:25)
at new ApolloServerBase (D:\react-training-graphql\node_modules\apollo-server-core\dist\ApolloServer.js:118:75)
at new ApolloServer (D:\react-training-graphql\node_modules\apollo-server-express\dist\ApolloServer.js:12:1)
at file:///D:/react-training-graphql/server/src/index.js:20:16
at ModuleJob.run (internal/modules/esm/module_job.js:152:23)
at async Loader.import (internal/modules/esm/loader.js:177:24)
at async Object.loadESM (internal/process/esm_loader.js:68:5)
[nodemon] app crashed - waiting for file changes before starting...
user
에서 대문자로 수정합니다. User
그런데 여전히 에러발생…
이건 버전업돼서 생기는 에러인듯 하다.
yarn run server
yarn run v1.22.10
$ yarn workspace server start
$ nodemon ./src/index.js
[nodemon] 2.0.12
[nodemon] to restart at any time, enter `rs`
[nodemon] watching path(s): src\**\*
[nodemon] watching extensions: js,mjs,json
[nodemon] starting `node ./src/index.js`
D:\react-training-graphql\server
D:\react-training-graphql\node_modules\apollo-server-core\dist\ApolloServer.js:288
throw new Error('You must `await server.start()` before calling `server.' +
^
Error: You must `await server.start()` before calling `server.applyMiddleware()`
at ApolloServer.assertStarted (D:\react-training-graphql\node_modules\apollo-server-core\dist\ApolloServer.js:288:19)
at ApolloServer.applyMiddleware (D:\react-training-graphql\node_modules\apollo-server-express\dist\ApolloServer.js:18:14)
at file:///D:/react-training-graphql/server/src/index.js:36:8
at ModuleJob.run (internal/modules/esm/module_job.js:152:23)
at async Loader.import (internal/modules/esm/loader.js:177:24)
at async Object.loadESM (internal/process/esm_loader.js:68:5)
[nodemon] app crashed - waiting for file changes before starting...
4.3 (추가) Apollo Server 3.0 업데이트 대응 - 환경 세팅 + playground
2021년 7월 17일, Apollo Server 3.0이 릴리즈가 되었습니다.
이 강의 영상을 보시면서 기존 작업된 것들을 수정하시면 되겠습니다.
위와 같이 apollo-server
, apollo-server-express
라이브러리의 버전이 업그레이드되었습니다.
이 강의를 올리던 시점에는 apollo-server
, apollo-server-express
라이브러리 버전이 2.25.1
이었는데, 2021년 9월 13일 기준 3.3.0
이 되었습니다.
yarn add apollo-server apollo-server-express express graphql
위 명령어로 최신 버전으로 다시 설치를합니다.
server/src/index.js
파일에서 몇가지 수정을 하겠습니다.
// server/src/index.js
import express from 'express'
// graphQL용 서버는 ApolloServer라는걸 이용할겁니다.
import {ApolloServer} from "apollo-server-express";
// import cors from 'cors' // cors 라이브러리 불러온거 삭제해줍니다.
import resolvers from './resolvers/index.js'
import schema from './schema/index.js'
import {readDB} from "./dbController.js";
// ApolloServer를 사용하면 아래 2줄 코드는 필요가 없게됩니다.
// app.use(express.urlencoded({extended: true}))
// app.use(express.json()) // express에서 json 형태로 사용하겠다.
// -------- app.use(cors( ... )) 아래 코드는 필요없으니 삭제해줍니다.
// app.use(cors({
// origin: 'http://localhost:3000', // 클라이언트 서버
// credentials: true,
// }))
// ApolloServer 서버를 생성합니다.
const server = new ApolloServer({
// typeDefs 라는 것이 필요하고, schema(스키마)라는게 필요합니다.
typeDefs: schema,
// 그리고 resolvers이라는 정보가 필요하게됩니다.
resolvers,
// 그리고 resolvers가 참조할 데이터.. 즉, DB 역할을하는 models 부분이 정의가 되어있어야 합니다.
context: {
db: {
// 아래 정의에 대해선 이따 하도록 하겠습니다.
messages: readDB('messages'),
users: readDB('users'),
}
}
});
const app = express(); // <- 이 코드 위치를 applyMiddleware 위쪽인 이 위치로 옮깁니다.
await server.start(); // <- 그리고 server.start() 코드를 await 키워드를 통해 실행하겠습니다.
// 위 정의가 다 되고나면, server.applyMiddleware() 함수를 통해서 아래와 같이 코드를 작성합니다.
server.applyMiddleware({
app,
path: '/graphql',
// 그리고 cors를 이전과 다르게 applyMiddleware의 3번째 인자(옵션값)로 객체형태로 넘겨줍니다.
cors: {
origin: 'http://localhost:3000', // 클라이언트 서버
credentials: true,
}
})
// 서버 경로는 8000번
// 그리고 아래도 await 키워드를 붙여주고 첫번째 인자 값을 아래와 같이 수정해줍니다.
await app.listen({port: 8000})
// 위의 await 붙은 코드가 끝나고 아래가 실행될거기 때문에 console.log(...)도 이렇게 밖으로 빼줍니다.
console.log('server listening on 8000...')
// 원래는 아래와 같은 모습이었음.
// app.listen(8000, () => {
// console.log('server listening on 8000...')
// })
위와 같이 수정을하면 이제 에러없이 실행이됩니다.
yarn run server
$ yarn workspace server start
$ nodemon ./src/index.js
[nodemon] 2.0.12
[nodemon] to restart at any time, enter `rs`
[nodemon] watching path(s): src\**\*
[nodemon] watching extensions: js,mjs,json
[nodemon] starting `node ./src/index.js`
D:\react-training-graphql\server
server listening on 8000...
4.3.2 apollo-server 3.0 playground
apollo-server
가 3.0
이 되면서 playground
도 내용이 좀 바뀌었습니다.
localhost:8000/graphql
url로 들어가면 아래와 같이 접속화면이 바뀌어있습니다.
Query your server
버튼을 클릭하면 아래와 같이 playground
가 열리게됩니다.
playground
가 이전 2점대 버전과 차이점이 CORS
를 별도로 설정해줘야된다라는 점입니다.
현재로써는 cors
가 https://studio.apollographql.com/
이 url에 대해서는 cors
를 못잡기 때문에 cross-origin
정책에 의해서 접근을 못하는 상태입니다.
위 cors
의 origin
부분에 정의할 때 주요사항이 있습니다.
url
끝 부분에 /
를 절대 작성하지 마십시오.
/
하나 붙어있는 것만으로 에러가납니다.
절대 붙이지 마세요.
// server/src/index.js
import express from 'express'
// graphQL용 서버는 ApolloServer라는걸 이용할겁니다.
import {ApolloServer} from "apollo-server-express";
// import cors from 'cors' // cors 라이브러리 불러온거 삭제해줍니다.
import resolvers from './resolvers/index.js'
import schema from './schema/index.js'
import {readDB} from "./dbController.js";
// ApolloServer를 사용하면 아래 2줄 코드는 필요가 없게됩니다.
// app.use(express.urlencoded({extended: true}))
// app.use(express.json()) // express에서 json 형태로 사용하겠다.
// -------- app.use(cors( ... )) 아래 코드는 필요없으니 삭제해줍니다.
// app.use(cors({
// origin: 'http://localhost:3000', // 클라이언트 서버
// credentials: true,
// }))
// ApolloServer 서버를 생성합니다.
const server = new ApolloServer({
// typeDefs 라는 것이 필요하고, schema(스키마)라는게 필요합니다.
typeDefs: schema,
// 그리고 resolvers이라는 정보가 필요하게됩니다.
resolvers,
// 그리고 resolvers가 참조할 데이터.. 즉, DB 역할을하는 models 부분이 정의가 되어있어야 합니다.
context: {
db: {
// 아래 정의에 대해선 이따 하도록 하겠습니다.
messages: readDB('messages'),
users: readDB('users'),
}
}
});
const app = express(); // <- 이 코드 위치를 applyMiddleware 위쪽인 이 위치로 옮깁니다.
await server.start(); // <- 그리고 server.start() 코드를 await 키워드를 통해 실행하겠습니다.
// 위 정의가 다 되고나면, server.applyMiddleware() 함수를 통해서 아래와 같이 코드를 작성합니다.
server.applyMiddleware({
app,
path: '/graphql',
// 그리고 cors를 이전과 다르게 applyMiddleware의 3번째 인자(옵션값)로 객체형태로 넘겨줍니다.
cors: {
origin: [
'http://localhost:3000',
'https://studio.apollographql.com/'
], // 클라이언트 서버
credentials: true,
}
})
// 서버 경로는 8000번
// 그리고 아래도 await 키워드를 붙여주고 첫번째 인자 값을 아래와 같이 수정해줍니다.
await app.listen({port: 8000})
// 위의 await 붙은 코드가 끝나고 아래가 실행될거기 때문에 console.log(...)도 이렇게 밖으로 빼줍니다.
console.log('server listening on 8000...')
// 원래는 아래와 같은 모습이었음.
// app.listen(8000, () => {
// console.log('server listening on 8000...')
// })
그래서 위와같이 cors origin
에 cross-origin
의 내용을 추가를 해줘야지만 playground
에 접속을 할 수 있게됩니다.
아니다. 강의와 똑같다. 이땐 내가 저 메시지를 클릭을 안해서 그랬었다.
메시지 클릭하면 강의 화면과 동일하게 나온다.
여튼 이 상태에서 가운데있는 query
는 할 수 있다고함. 음… 아니네?
아까 위에서 말했듯이 url
맨 끝에 /
붙어있는지 확인해!!
4.4 GraphQL Playground
localhost:8000/graphql
로 접속해보겠습니다.
처음에 나오는 화면에서 Query your server
버튼을 누르면 playground
로 들어갈 수 있습니다.
playground
상에서 schema
에 해당하는 정보를 그대로 요청할 수 있습니다.messages
요청을 위한 Query
를 날릴 수 있습니다.
cors
의 origin
의 url
마지막에 /
붙어있는지 확인!?!?!?!?!?
Unable to reach server
에 대한 해결을 하고나서 실습해야될거 같다.
공부하다가 이 시점에서 알게됨..CORS
에러가 해결이 안되었던게..
아니다. 아직 CORS
가 해결이 안된 것이었네..
다시 해봐야겠네.. ㅠ
이때알게됨… 뒤에 /
슬래쉬 지웠더니 됨… ㅠㅠ
위와 같이 shcema
에 정의한대로 query Messages
를 보냈더니 메시지들에 대한 정보가 원하는대로 온다.
여기서 중요한 점은 GraphQL
은 내가 원하는 프로퍼티키 값들만 받아올 수 있다는 것이다.REST API
에선 id
, text
, timestamp
, userId
모두 받아왔었다.
하지만 GraphQL
에선 내가 원하는 키들만 선택해서 받아올 수 있다.
여튼 위와 같이 데이터가 잘 나오는 것을 확인하실 수 있을겁니다.
만약에 프론트엔드 개발자 입장에서 ‘이번에는 timestamp
까진 필요없을거같아', id
와 userId
만 있으면 될거같아라고 생각한다면,
아래와 같이 Query
를 작성해서 날리면됩니다.
위와 같이 원하는 프로퍼티키들만 골라내 받아올 수 있습니다.
위와 같이 서버에서 주는 데이터 자체가, 사용자가 요청한 것들만, 즉, 필요한 것들만 주도록 되어있는겁니다. GraphQL
에선.
그래서 REST API
경우에는 오버 엔지니어링이 됐을 때, 프론트엔드가 요청을 할 때,
서버에서 주는 정보를 그대로 들고와서 클라이언트에서 취사선택을 했어야했지만,
GraphQL
의 경우는 서버에서부터 이미 취사선택된 데이터가 내려옵니다.
messages
에 대한 요청은 성공했고 message
에 대한 요청도 해보겠습니다.
이번엔 message
하나에 대한 요청입니다.
// 아래와 같이 schema에 정의한 형식 똑같이 작성해주고
query Messages($id: ID!) {
message(id: $id) {
id
userId
}
}
// Variables 탭에 원하는 id값을 넣어 인자로 전달해 요청합니다.
{
"id" : "...w"
}
요청을 보내면 이렇게 위와 같이 요청결과가 반환됩니다.
message
하나만 요청하는 것도 성공했습니다.
위와 같이 이미 저희가 schema
에 Query
를 다 만들어놨기 때문에 이런것들이 가능한겁니다.
Mutation
도 playground
에서 바로 보낼 수 있습니다.
Mutation
으로 createMessage()
를 보내볼게요.
위와 같이 Mutation
요청도 보낼 수 있습니다.Query
요청 보내듯이 똑같은 형식으로 작성하고 Variables
에 필요한 값인 text
와 userId
를 작성하여 요청합니다.
전체 메시지를 불러오는 요청을 하면, 방금 작성한 메시지가 DB
에 추가되었음을 확인할 수 있습니다.
server/src/db/messages.json
파일에도 기록된 것을 확인할 수 있습니다.
이렇게 GraphQL
은 서버에서 만들어놓은걸 즉시 확인해볼 수 있다는 장점이 있습니다.
문법적으로 좀 헷갈리는 부분이 있지만 이거는 익숙해지면 되는 문제라고 생각합니다.
GraphQL
로 작성하는 서버쪽 작업이 모두 완료가되었습니다.
나중에 아까 말씀드린대로 무한스크롤을 구현하기위해서 server/src/db/messages.json
파일에서 갯수제한을 둬서 몇개씩 데이터 뽑아오고 이런거는 나중에 다시 작업을 하도록 하겠습니다.
바로 이어서 client
의 작업도 이어가겠습니다.
GraphQL
에선 client
에서 수정할 내용이 의외로 많습니다.