📌 그래프큐엘과 express를 이용해 아주아주아주아주 기본적인 서버를 만들어봅니다!

1. 설치 먼저 하실게요!

아래의 package.json을 작성한 다음 yarn해주셔도 되고, devDependencies가 몇 개 되지 않으니 yarn add -D로 직접 설치하셔도 됩니다!

본 게시물의 예제는 깃헙(클릭)에서도 확인하실 수 있습니다. 리드미에서 간단한 명세서도 보실 수 있으니, 클라이언트 분들도 연습용을로 사용하실 수 있을지도!

{
  "name": "graphql",
  "version": "1.0.0",
  "main": "index.js",
  "repository": "git@github.com:soryeongk/graphql.git",
  "license": "MIT",
  "scripts": {
    "start": "node server.js"
  },
  "devDependencies": {
    "express": "^4.18.1",
    "express-graphql": "^0.12.0",
    "graphql": "^16.4.0",
  }
}

2. express 설정

본 게시물은 express-graphql을 설명하고 있습니다. 때문에 아주아주 기본적인 express 설정을 먼저 해줄게요!

2-1. Start Server Listening

// server.js
const express = require('express');
const PORT = 3000;

const app = express();

app.use('/');

app.listen(PORT, () => {
  console.log(`
  ##############################################################
	🎉 This is soryeongk's kawaii server with express-graphql
      		🛡️ Server listening on port ${PORT} 🛡️
  ##############################################################
`);
});

2-2. 스키마 설정

스키마는 타입들을 모아둔 것을 말합니다. 어떤 데이터들을 어떤 데이터로 담을지에 대해서 정의하는 것이죠! 사용법은 아주 직관적이고 간단합니다!

📌 본 게시물에서 만들어볼 예제는 The SOPT 30기 그래프큐엘 스터디원들의 정보를 다루는 서버입니다. The SOPT는 국내 최대 규모의 IT 벤처 창업 동아리로, 기획/디자인/서버/웹/iOS/안드로이드 파트로 나눠져있습니다. :))

// schema.js
const Graphql = require('graphql');

const gqlMemberSchema = Graphql.buildSchema(`
  enum SoptParts {
    PLAN
    DESIGN
    SERVER
    WEB
    iOS
    ANDROID
  }
  type GqlMember {
    id: ID!
    name: String!
    latestSopt: Int
    part: SoptParts!
    isOnDiet: Boolean
  }
`);

module.exports = gqlMemberSchema;

3. 본격 서버 뚝딱거리기

end point로 들어오는 요청에 맞게 내용을 return 해주는 resolver를 만들어봅시다!
일단 그큐엘이 어떻게 동작하는지 보기 위해서 test라는 요청에 대해서 메시지를 전달하는 resolver를 만들어보겠습니다.

const resolver = {
  test: () => {
    return "This is soryeongk";
  },
};

module.exports = resolver;

이래도 되나.. 이게 맞나.. 싶을 정도로 쉽죠!
이 resolver를 연결하면 test라는 요청에 대해서 무조건 This is soryeongk라는 결과를 반환해줍니다! 직관적이네요!

이제 그큐엘 스터디원들의 정보를 담아보려하는데, DB를 연결하기가 너무 귀찮..네요.. 그래서 DB를 흉내낼 수 있는 것을 만들어보려 합니다. 아래 아이디어는 The Beginner Developer님의 Medium 게시글(클릭)을 참고했습니다.!

3-1. DB 흉내내기

스터디원들에 대한 resolver를 만들기 전, 우리는 데이터베이스를 굳이 연결하지 않고, 아래와 같이 가상의 공간을 만들어 사용하려 합니다. 이름이 너무 별로지만, 이거 만들 때 너무 졸렸기 때문에 어쩔 수 없습니다. GqlMembers에는 id를 key 로 하는 멤버들의 정보가 담길 것입니다. 그 정보들을 담는 틀은 GqlMember라는 클래스로 만들어 관리합니다. 필수 정보인 id name, part를 담습니다.

class GqlMember {
  constructor(id, { name, part }) {
    this.id = id;
    this.name = name;
    this.part = part;
  }
}

const GqlMembers = {};

3-2. 스터디원 정보 조회하기

조회를 위한 쿼리 작성은 매우 쉽습니다!

const resolver = {
  test: () => {
    return "This is soryeongk";
  },
  getGqlMember: ({ id }) => {
    return GqlMembers[id];
  },
};

module.exports = resolver;

스키마에는 다음 내용이 추가되어야합니다. Query의 작동 방식을 명시합니다!

type Query {
    getGqlMember(id: ID!): GqlMember
  }

이제 getGqlMember라는 엔드포인트와 함께 id를 전달하면 GqlMembers에서 해당 id에 해당하는 멤버의 정보를 그대로 return합니다. 이 때, overFetching을 막을 수 있도록, 클라이언트에서는 필요한 정보만을 선택적으로 요청할 수 있습니다. 아래와 같이 요청을 보내면 해당 id의 멤버가 다이어트 중인지 아닌지를 알 수 있습니다.

{
  getGqlMember(id: "e877dbd0472163d7f268") {
    isOnDiet
  }
}

3-3. 스터디원 추가하기

새로운 스터디원이 생겼어요! 이 사람의 정보를 담아서 DB에 추가해줘야해요! 새로운 멤버의 정보가 전달되면 id값을 부여하고 해당 내용을 DB에 저장해요! 근데 그럼 타입이 조금 달라지겠죠? 원래 우리 멤버들의 정보에는 id가 기본적으로 포함되어야있어야하는데, 새로운 멤버의 경우 처음 전달되는 정보에는 id가 없기 때문입니다.

그래서 우리는 input에 대한 타입을 새로 지정해줘야합니다. 스키마를 추가해봅시다!

input NewGqlMember {
	name: String!
  latestSopt: Int
  part: SoptParts!
  isOnDiet: Boolean
}

type Mutation {
  addGqlMember(newInput: NewGqlMember): GqlMember
}

눈치 채신 분들도 있을까요? 제가 30분 정도를 빡쳐있던 이유였는데 새로운 input에 대한 타입은 type이 아닌 input으로 명시합니다! 이것을 토대로 resolver를 만들면 다음과 같습니다.

const resolver = {
  test: () => {
    return "This is soryeongk";
  },
  getGqlMember: ({ id }) => {
    return GqlMembers[id];
  },
  addGqlMember: ({ newInput }) => {
    const id = require('crypto').randomBytes(10).toString('hex');
    GqlMembers[id] = new GqlMember(id, newInput);
    
    return GqlMembers[id];
  },
};

module.exports = resolver;

3-4. 스터디원 정보 업데이트

현재는 서버파트를 수료 중인 소령이(soryeongk)는 웹파트에서 너무 즐거웠는지, 자꾸 파트명에 WEB을 적는 령이 때문에 정보를 수정해야해요! 령이의 id값과 함께 수정된 정보를 함께 정달하면 데이터가 업데이트되는 쿼리를 만들어봅시다!

const resolver = {
  test: () => {
    return "This is soryeongk";
  },
  getGqlMember: ({ id }) => {
    return GqlMembers[id];
  },
  addGqlMember: ({ newInput }) => {
    const id = require('crypto').randomBytes(10).toString('hex');
    GqlMembers[id] = new GqlMember(id, newInput);
    
    return GqlMembers[id];
  },
  updateGqlMember: ({ id, modified }) => {
    GqlMembers[id] = new GqlMember(id, modified);
    return GqlMembers[id];
  },
};

module.exports = resolver;

스키마의 mutation 타입도 다음과 같이 추가해줍니다.

type Mutation {
  addGqlMember(newInput: NewGqlMember): GqlMember
	updateGqlMember(id: ID!, modified: NewGqlMember): GqlMember
}

modified로 전달된 내용을 덮어쓰도록 했습니다! PATCH가 아닌 PUT으로 작동하는 것이 아쉽네요,,

3-5. 스터디원 정보 삭제

아아 누가 그래프큐엘 배우다가 모종의 이유로 스터디를 나가게 되었습니다. 서비스마다 휴면계정 또는 회원탈퇴가 있는 것처럼 우리도 이 사람의 개인정보(한 사람을 식별할 수 있는 정보)를 계속 가지고 있을 수가 없습니다..!

생각해보니 이름과 솝트 내 소속 파트를 알고 있는 것만으로도 한 사람을 특정할 수 있는 가능성이 매우 높아지므로 우리는 꽤나 민감한 정보를 다루는 db네요 ㅎㅎ 더욱이 삭제 쿼리를 만들어야겠습니다.

const resolver = {
  test: () => {
    return "This is soryeongk";
  },
  getGqlMember: ({ id }) => {
    return GqlMembers[id];
  },
  addGqlMember: ({ newInput }) => {
    const id = require('crypto').randomBytes(10).toString('hex');
    GqlMembers[id] = new GqlMember(id, newInput);
    
    return GqlMembers[id];
  },
  updateGqlMember: ({ id, modified }) => {
    GqlMembers[id] = new GqlMember(id, modified);
    return GqlMembers[id];
  },
  deleteX: ({ id }) => {
    delete GqlMembers[id];
    return `DELETED ${id}`;
  }
};

module.exports = resolver;

4. graphql와 express 연결

자아- 이제 그큐엘로 스터디원의 정보를 다루는 것이 가능해졌으니, 아까 열어둔 서버에서 이 내용을 사용할 수 있도록 연결해보겠습니다.
graphqlHTTP에 schema, rootValue를 전달하면 연결이 완료됩니다! 그리고 graphiql 옵션을 true로 설정하면 해당 라우터에서 playground를 실행할 수 있습니다.

const express = require('express');
const { graphqlHTTP } = require('express-graphql');

const resolver = require('./resolver');
const gqlMemberSchema = require('./schema');

const PORT = 3000;

const app = express();

app.use('/graphql', graphqlHTTP({
  schema: gqlMemberSchema,
  rootValue: resolver,
  graphiql: true,
}));

app.listen(PORT, () => {
  console.log(`
  ################################################
       🎉 This is soryeongk's kawaii server
      🛡️ Server listening on port ${PORT} 🛡️
  ################################################
`);
});

graphiql: true 에 대하여
GraphQL 엔드포인트가 브라우저에 로드될 때 GraphiQL을 표시합니다. 앱이 개발 중일 때는 매우 유용하므로 graphiql을 true로 설정하는 것이 좋습니다. 프로덕션에서 원할 수도 있고 원하지 않을 수도 있습니다. 또는 true 대신 options 객체를 전달할 수 있습니다.
아래와 같이 graphqlHTTP에는 다양한 옵션들이 있으니 확인 후 적절하게 사용하는 것을 추천합니다.

관련 GitHub 링크(클릭)

profile
웹 프론트엔드 개발자 령이의 어쩌구 저쩌구

0개의 댓글