Programming/기타

GraphQL Apollo 서버와 MySQL 연동

armyost 2023. 4. 1. 06:21
728x90

※ NPM 패키지 매니저 사용

※ 원문 참조(본 포스팅은 원문의 상당량을 참조하였습니다.)

https://hoons-up.tistory.com/53

 

[Develop/Node&Express] Express / GraphQL / Appllo 맛보기 2 (Mysql)

Express / GraphQL / Appllo 맛보기 2 (Mysql) 이번에는 Expess, GraphQL, Apollo-Server, Mysql을 사용해서 Express와 Mysql 연결과 Apollo-Server를 사용한 GraphQL 서버를 간단하게 만들어 볼려고한다. 추후 다음 포스팅을 통

hoons-up.tistory.com

 

소스코드 : https://github.com/armyost/graphql-appllo

 

GitHub - armyost/graphql-appllo

Contribute to armyost/graphql-appllo development by creating an account on GitHub.

github.com

 

최종 폴더 트리 구조



매뉴얼

 

1. 프로젝트용 폴더 생성 후 이동

 

2. npm init 으로 프로젝트 생성


3. NPM 라이브러리 설치

$ npm install nodemon --save-dev
$ npm install --save-dev @babel/core @babel/node @babel/preset-env
$ npm install apollo-server-express body-parser-graphql compression express promise-mysql


4. Babel 설정
Es6 문법을 사용하여 개발하기 위해서 Babel 설정을 진행

/.babelrc

// .babelrc 파일
{
  "presets": ["@babel/preset-env"] // 바벨 플러그인 모음
}

 

5. Express, ApolloServer 설정
먼저 src 폴더를 생성하고 src 폴더 안에 app.js 파일을 생성한다.
아래와 같은 과정으로 ApolloServer 서버를 생성하고 express를 ApolloServer의 미들웨어로 추가하여
graphql EndPoint를 생성한다.

 

/src/app.js

// src 안에 app.js 파일
import express from "express";
import { ApolloServer } from "apollo-server-express";
import { bodyParserGraphQL } from "body-parser-graphql";
import compression from "compression";
import resolvers from "../src/graphql/resolvers";
import fs from "fs";

// Node file system을 사용하여 gql schema 가져옴
const typeDefs = fs.readFileSync("src/graphql/schema.graphql", {
  encoding: "utf-8",
});

const port = 8000;
const app = express();

app.use(bodyParserGraphQL());
app.use(compression());

// ApolloServer 생성
const server = new ApolloServer({
  typeDefs,
  resolvers,
  introspection: true, // 스키마 검사 활성화 default: true
  playground: true, // playgorund 활성화 default: true
});

server.start().then(res => {
    server.applyMiddleware({ app, path: '/graphql' });
  });

app.listen(port, async () => {
  console.log("graphql api server open");
});

bodyParserGraphQL과 compression을 미들웨어로 추가하여 사용했다.
bodyParserGraphQL : gql(쿼리)를 해석하기 위해 사용.
compression : gzip 압축을 사용하여 웹 앱의 속도를 높이기 위하여 사용.

 

6. GraphQL 설정
이번엔 src 하위에 graphql 폴더를 만들고 GraphQL 관련 파일들을 넣어보자.
두가지 파일을 생성할 것인데. GraphQL 스키마 언어(GraphQL schema language)를 정의하는 schema.graphql 파일과
스키마에 정의한 것들을 데이터를 불러오고 어떤 식으로 가공하여 전달할 것인지를 정하는 resolvers 파일이다.

GrapthQL 스키마 타입은 공식 문서에 보기 좋게 나와있다.
https://graphql-kr.github.io/learn/schema

 

GraphQL: API를 위한 쿼리 언어

GraphQL은 API에 있는 데이터에 대한 완벽하고 이해하기 쉬운 설명을 제공하고 클라이언트에게 필요한 것을 정확하게 요청할 수 있는 기능을 제공하며 시간이 지남에 따라 API를 쉽게 진화시키고

graphql-kr.github.io

 

GraphQL 타입에는 일반적으로 객체타입을 많이 사용하겠지만 특수한 두 가지 타입이 Query, Mutation으로
Query는 데이터를 읽고(R). Mutation은 등록수정삭제(CUD)를 정의한다.

간단하게 User의 목록을 불러오고 추가해보려고 한다. 먼저 개인 로컬 서버에 Mysql DB를 생성하고 User테이블에
userName과  userId 필드를 생성하여 준다.

 

/src/graphql/schema.graphql

type User {
  userId: String
  userName: String
}

type Query {
  users: [User]
}

type Mutation {
  addUser(userId: String!, userName: String!): Boolean
}

User라는 객체타입을 정의하고 userId와 userName을 String 타입으로 정의하였다. String!처럼 !가 붙은 타입은
꼭 받아야하는 타입임을 명시한다.
테스트를 해보면서 GraphQL 타입 정의는 타입 스크립트의 타입 정의와 유사하다고 생각했다. 

 

 

/src/graphql/resolvers.js

import user from "../db/models/user";
import StatusCode from "../constants/statusCode";

const resolvers = {
  Query: {
    users: async () => {
      const result = await user.selectAll();
      return result.data;
    },
  },

  Mutation: {
    addUser: async (_, { userId, userName }) => {
      const result = await user.insert(userId, userName);
      return result.code === StatusCode.OK ? true : false;
    },
  },
};

export default resolvers;

위에는 간단하게 resolver를 만들어서 Query에 유저목록를 조회하는 users를 만들고 Mutations에는 유저를 등록하는
addUser를 정의한 스키마에 맞게 작성했다. StatusCode는 간단하게 Constant 정의한 것으로 굳이 만들 필요는 없지만
src 하위에 constatns 폴더를 만들고 아래와 같이 간단하게 구현해놨다.

 

 

/src/constants/statusCode.js

const statusCode = {
  OK: 200,
  ERORR: 400,
};

export default statusCode;

또 DB를 통해 받아온 데이터의 성공여부와 결괏값을 확인하기 위해 간단하게 util을 생성했다. 이 부분 또한 밑에서 DB 데이터 조회 시 만들어 사용해도 되고 안 해도 무관하다. 해당 파일은 src 하위에 utils 폴더를 만들고 아래와 같이 구현해놨다.

 

/src/utils/statusUtil.js

import StatusCode from "../constants/statusCode";

const statusUtil = {
  success: (data) => {
    return { code: StatusCode.OK, data: data };
  },
  false: () => {
    return { code: StatusCode.ERORR };
  },
};

export default statusUtil;

여기까지 GraphQL 사용해 User 목록과 추가를 할수 있도록 스키마와 리졸버를 생성했다.
마지막으로 Express와 Mysql을 connection 맺어 DB의 데이터를 가져와 resolver가 데이터를 사용하도록 해보자.

 

7. Mysql(DB) Connection 설정
promise-mysql를 사용하여 async/await를 사용하여 connection을 맺고 함수를 생성하여 생성하였다.
먼저 src 하위에 db 폴더를 생성하고 아래 작업을 진행한다.

 

/src/db/config.js

import mysql from "promise-mysql";

const dbConfig = {
  host: "localhost",
  port: 3306,
  user: "유저정보",
  password: "패스워드정보",
  database: "graphql_test",
};

export default mysql.createPool(dbConfig);

 

/src/db/pool.js

import poolPromise from "./config";

const pool = {
  query: async (query, value) => {
    let result;
    const pool = await poolPromise;
    try {
      var connection = await pool.getConnection();
      result = value
        ? await connection.query(query, value)
        : (await connection.query(query)) || null;
    } catch (err) {
      console.log(err);
      connection.rollback(() => {});
    } finally {
      pool.releaseConnection(connection);
      return result;
    }
  },
};

export default pool;

pool을 생성하고 connection하여 데이터를 받아와 connection을 종료하기까지 과정을 반복해서 하지 않기 위해
위와 같이 함수로 정의하였다.

마지막으로 models 폴더를 만들고 각 테이블 별로 필요한 SQL를 작성하고 데이터를 읽어 오도록 했다.
지금은 User 관련 테이블만 있기에 models 폴더 안에 user.js 파일만 만든다.

 

/src/db/models/user.js

import statusUtil from "../../utils/statusUtil";
import pool from "../pool";

const tableName = "user";

const user = {
  selectAll: async () => {
    const query = `SELECT * FROM ${tableName}`;
    const result = await pool.query(query);

    return result ? statusUtil.success(result) : statusUtil.false();
  },
  insert: async (userId, userName) => {
    const query = `INSERT INTO ${tableName} (userId, userName) VALUES (?, ?)`;
    const result = await pool.query(query, [userId, userName]);

    return result ? statusUtil.success(result) : statusUtil.false();
  },
};

export default user;

전체 사용자를 불러오는 함수와 사용자를 등록하는 함수 두 개를 정의하고 각 쿼리를 작성했다. 
위에 pool.js에 보면 value가 있을 경우 connection에 두 번째 인자로 배열을 넘겨주는데 insert 쿼리에서
INSERT INTO ${tableName} (userId, userName) VALUES (?, ?)  ? 로 작성된 값이 자동으로 매핑되어 들어간다.
위에서는 userId와 userName이 자동으로 매핑되게 된다.
이렇게 작성된 함수들은 resolver에서 사용하여 데이터를 읽는다.

 

8. 실행스크립트 등록

이제 서버를 실행할 수 있도록 스크립트를 작성해보자 package.json에 아래와 같이 start를 만들어준다.
nodemon과 babel을 사용하고 만들었던 app.js를 실행한다.

  "scripts": {
    "start": "nodemon --exec babel-node src/app.js",
    ....
  },
  ....생략

 

9. 콘솔 접속 테스트

localhost:8000/graphql로 접속

mutation {
addUser(userId: "3", userName: "JPKIM")
}