Koa 서버에서 GraphQL을 활용하려면 다음 단계별로 진행하면 된다.
1. 필수 패키지 설치
먼저 필요한 패키지들을 설치한다. graphql 라이브러리와 Koa 미들웨어가 필요하다.
npm install graphql koa-graphql koa-mount --save
2. 프로젝트 구조
전체 구조는 다음과 같이 구성한다:
├── app.js # 메인 서버 파일
├── schema/
│ └── query.js # GraphQL 스키마 정의
└── model/
└── database.js # MongoDB 유틸리티
3. 메인 서버 파일 구현
Koa 인스턴스를 생성하고 GraphQL 미들웨어를 설정한다.
// app.js
const Koa = require('koa');
const router = require('koa-router')();
const mount = require('koa-mount');
const graphqlHTTP = require('koa-graphql');
const GraphQLQuerySchema = require('./schema/query.js');
const app = new Koa();
// GraphQL 엔드포인트 설정
app.use(mount('/api/graphql', graphqlHTTP({
schema: GraphQLQuerySchema,
graphiql: true
})));
app.use(router.routes());
app.use(router.allowedMethods());
app.listen(5000);
console.log('GraphQL 서버가 http://localhost:5000/api/graphql 에서 실행 중');
4. GraphQL 스키마 정의
스키마 파일에서는 쿼리 타입과 리졸버 함수를 정의한다.
// schema/query.js
const Database = require('../model/database.js');
const {
GraphQLObjectType,
GraphQLString,
GraphQLInt,
GraphQLList,
GraphQLSchema
} = require('graphql');
// 상품 스키마 타입 정의
const ProductType = new GraphQLObjectType({
name: 'Product',
fields: {
id: {
type: GraphQLString,
resolve: (parent) => parent._id.toString()
},
name: {
type: GraphQLString
},
price: {
type: GraphQLInt
},
description: {
type: GraphQLString
},
createdAt: {
type: GraphQLString,
resolve: (parent) => new Date(parent.add_time).toISOString()
}
}
});
// 루트 리졸버 정의
const RootResolver = new GraphQLObjectType({
name: 'QueryRoot',
fields: {
// 전체 상품 목록 조회
products: {
type: GraphQLList(ProductType),
async resolve() {
const products = await Database.find('products', {});
return products;
}
},
// 단일 상품 조회
product: {
type: ProductType,
args: {
id: {
type: GraphQLString,
description: '상품 ID'
}
},
async resolve(parent, args) {
const [product] = await Database.find('products', {
_id: Database.createObjectId(args.id)
});
return product;
}
},
// 카테고리별 상품 조회
productsByCategory: {
type: GraphQLList(ProductType),
args: {
category: {
type: GraphQLString
}
},
async resolve(parent, args) {
return await Database.find('products', {
category: args.category
});
}
}
}
});
// 스키마 생성 및エクスポート
module.exports = new GraphQLSchema({
query: RootResolver
});
5. 데이터베이스 유틸리티 구현
MongoDB 연동을 위한 헬퍼 클래스를 생성한다. Singleton 패턴을 사용하여 데이터베이스 연결을 관리한다.
// model/database.js
const MongoDB = require('mongodb');
const MongoClient = MongoDB.MongoClient;
const ObjectID = MongoDB.ObjectID;
const Config = {
dbUrl: 'mongodb://127.0.0.1:27017/',
dbName: 'shop'
};
class Database {
static getInstance() {
if (!Database.instance) {
Database.instance = new Database();
}
return Database.instance;
}
constructor() {
this.dbClient = null;
this.connect();
}
connect() {
const self = this;
return new Promise((resolve, reject) => {
if (!self.dbClient) {
MongoClient.connect(Config.dbUrl, { useNewUrlParser: true }, (err, client) => {
if (err) {
reject(err);
return;
}
self.dbClient = client.db(Config.dbName);
resolve(self.dbClient);
});
} else {
resolve(self.dbClient);
}
});
}
find(collectionName, query, options = {}) {
const { fields = {}, page = 1, pageSize = 100, sort = {} } = options;
return new Promise((resolve, reject) => {
this.connect().then((db) => {
const cursor = db.collection(collectionName)
.find(query, { projection: fields })
.skip((page - 1) * pageSize)
.limit(pageSize)
.sort(sort);
cursor.toArray((err, docs) => {
if (err) {
reject(err);
return;
}
resolve(docs);
});
});
});
}
insert(collectionName, data) {
return new Promise((resolve, reject) => {
this.connect().then((db) => {
db.collection(collectionName).insertOne(data, (err, result) => {
if (err) {
reject(err);
return;
}
resolve(result);
});
});
});
}
update(collectionName, query, data) {
return new Promise((resolve, reject) => {
this.connect().then((db) => {
db.collection(collectionName).updateOne(query, {
$set: data
}, (err, result) => {
if (err) {
reject(err);
return;
}
resolve(result);
});
});
});
}
delete(collectionName, query) {
return new Promise((resolve, reject) => {
this.connect().then((db) => {
db.collection(collectionName).deleteOne(query, (err, result) => {
if (err) {
reject(err);
return;
}
resolve(result);
});
});
});
}
createObjectId(id) {
return new ObjectID(id);
}
count(collectionName, query) {
return new Promise((resolve, reject) => {
this.connect().then((db) => {
db.collection(collectionName).countDocuments(query, (err, count) => {
if (err) {
reject(err);
return;
}
resolve(count);
});
});
});
}
}
module.exports = Database.getInstance();
6. GraphiQL 인터페이스 확인
서버를 실행한 후 브라우저에서 http://localhost:5000/api/graphql 에 접근하면 GraphiQL 쿼리 인터페이스를 사용할 수 있다. 다음과 같은 쿼리로 데이터를 테스트할 수 있다:
{
products {
id
name
price
}
}
핵심 포인트 정리
- koa-mount: 특정 경로에 미들웨어를 마운트하는 데 사용
- koa-graphql: Koa에서 GraphQL 핸들러를 실행
- GraphQLObjectType: 스키마의 타입을 정의
- 리졸버: 실제 데이터 조회 로직을 구현
- Singleton 패턴: 데이터베이스 연결을 효율적으로 관리
이제 Koa와 GraphQL이 통합된 API 서버를 구축할 수 있다. 더 복잡한 쿼리나 뮤테이션이 필요하다면 같은 구조에서 타입과 리졸버만 확장하면 된다.