1. monorepo 프로젝트 구성
프로젝트의 디렉토를 생성해준다.
<shell />mkdir typeorm-apollo-server-example
yarn berry를 사용할 예정이기 때문에, 아래 명령어로 berry
버전으로 변경해준다.
<shell />yarn set version berry
아래에서 apollo server를 오류없이 원활하게 구축하기 위해, .yarnrc.yml의 nodeLinker를 node-modules로 수정해준다.
<typescript />
nodeLinker: node-modules
yarnPath: .yarn/releases/yarn-3.2.3.cjs
yarn init
명령어로 monorepo 프로젝트를 초기 설정한다.
<shell />yarn init -y
package.json이 생성이 되면, workspace 경로를 정의하고, 편의를 위해 scripts를 추가한다.
<javascript />
// package.json
{
"name": "typeorm-apollo-server-example",
"version": "0.1.0",
"packageManager": "yarn@3.2.3",
"workspaces": {
"packages": ["packages/**"]
},
"scripts": {
"server": "yarn workspace @typeorm-apollo-server-example/server"
}
}
monorepo 초기 설정은 완료!
2. apollo server 서버 구축
server 디렉토리를 생성하고 프로젝트 초기 설정을 해준다.
<shell />mkdir -p packages/server cd packages/server yarn init -y
server/package.json이 생성이 되면 아래와 같이 수정해준다.
<javascript />
// server/package.json
{
"name": "@typeorm-apollo-server-example/server",
"version": "0.1.0",
"packageManager": "yarn@3.2.3",
"scripts": {
"dev": ""
}
}
2.1. 의존성 추가
apollo-express-server 구축을 위해 필요한 의존성 라이브러리들을 추가해준다.
<shell />yarn install yarn server add apollo-server-express apollo-server-core express graphql
typescript를 사용할 예정이기 때문에 typescript와 관련된 라이브러리를 추가한다.
<shell />yarn server add --dev typescript @types/node ts-node
postgres와 apollo server 연동을 위해 typeorm
을 사용할 예정이기 때문에, 의존성을 추가해준다. (postgres
도 같이 추가)
<shell />yarn server add typeorm reflect-metadata yarn server add pg
typegraphql도 사용하기 위해 의존성을 추가한다.
<shell />yarn server add class-validator type-graphql
개발을 편하게 하기 위해 nodemon과 환경변수 조작을 위해 dotenv를 설치한다.
<shell />yarn server add --dev nodemon dotenv
필요한 의존성을 모두 추가했다!
2.2. typescript 설정
tsconfig.json을 생성하여 typescript 설정을 한다.
<typescript />
// tsconfig.json
{
"compilerOptions": {
"target": "es2018",
"module": "commonjs",
"lib": ["es2018", "esnext.asynciterable"],
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"outDir": "./dist",
"sourceMap": true,
"esModuleInterop": true,
"removeComments": true,
"isolatedModules": true
},
"exclude": [
"node_modules"
],
"include": [
"src"
]
}
- typeorm과 typegraphql을 원활하게 사용하기 위해 아래 옵션은 반드시 추가해줘야 함
- "target": "es2018"
- "lib": ["es2018", "esnext.asynciterable"]
- "emitDecoratorMetadata": true
- "experimentalDecorators": true
2.3. 서버 개발
src/index.ts를 생성하여 서버 실행 코드를 작성한다.
<typescript />
// server/index.ts
import 'reflect-metadata';
import { ApolloServer } from 'apollo-server-express';
import { ApolloServerPluginDrainHttpServer, ApolloServerPluginLandingPageLocalDefault } from 'apollo-server-core';
import express from 'express';
import http from 'http';
const startApolloServer = async () => {
const app = express();
const httpServer = http.createServer(app);
const server = new ApolloServer({
csrfPrevention: true,
cache: 'bounded',
plugins: [
ApolloServerPluginDrainHttpServer({ httpServer }),
ApolloServerPluginLandingPageLocalDefault({ embed: true }),
],
});
await server.start();
server.applyMiddleware({ app });
httpServer.listen(4000, () => console.log(`🚀 Server ready at http://localhost:4000${server.graphqlPath}`));
};
startApolloServer();
- 주의할점은 typeorm과 typegraphql을 사용하기 위해,
import 'reflect-metadata';
를 반드시 추가해줘야 함
2.4. typegraphql 을 이용하여 SDL과 리졸버 구현
2.4.1. SDL 구현
원래는 graphql 파일을 생성해서 SDL를 작성해야 하지만, typegraphql을 사용하면 객체 형태로 작성할 수 있다.
<typescript />
// src/entities
import { Field, ID, ObjectType } from 'type-graphql';
@ObjectType()
class User {
@Field(() => ID)
id: number;
@Field(() => String)
email: string;
@Field(() => String)
name: string;
@Field(() => String, { nullable: true })
address: string;
}
export default User;
2.4.2. 리졸버 구현
마찬가지로 리졸버도 어노테이션을 이용하여 빠르게 구현이 가능하다.
<typescript />
// src/resolvers/user-resolver.ts
import { Arg, Mutation, ID, Query, Resolver } from 'type-graphql';
import User from '../entities/user';
// TODO 실제 DB에서 조회/삽입할 수 있도록 수정 예정
const mockData = [
{ id: '1', email: 'tester@gmail.com', name: 'tester', address: null },
{ id: '2', email: 'tester2@gmail.com', name: 'tester2', address: null },
{ id: '3', email: 'tester3@gmail.com', name: 'tester3', address: null },
];
@Resolver()
class UserResolver {
@Query((returns) => User)
async user(@Arg('id') id: string) {
return mockData.find((user) => user.id === id);
}
@Query((returns) => [User])
async users() {
return mockData;
}
@Mutation((returns) => User)
async createUser(
@Arg('email') email: string,
@Arg('name') name: string,
@Arg('address', { nullable: true }) address?: string
) {
const createdUser = {
id: (mockData.length + 1).toString(),
email,
name,
address,
};
mockData.push(createdUser);
return createdUser;
}
}
export default UserResolver;
- Query 리졸버를 구현할때는
@Query
어노테이션을 사용하고 인자로 return 값을 지정 - Mutation 리졸버를 구현할때는
@Mutation
어노테이션을 사용하고 인자로 return 값을 지정 - request의 인자를 받아야한다면
@Arg
어노테이션을 사용- 만약, 필수 값이 아니라면 두번째 인자로 { nullable: true } 를 추가해주면 됨
이렇게 만든 SDL과 resolver를 apollo server에 넘겨주기 위해 index.ts 파일을 아래와 같이 수정한다.
<typescript />
// src/index.ts
import 'reflect-metadata';
import { ApolloServer } from 'apollo-server-express';
import { ApolloServerPluginDrainHttpServer, ApolloServerPluginLandingPageLocalDefault } from 'apollo-server-core';
import express from 'express';
import http from 'http';
import { buildSchema } from 'type-graphql';
import UserResolver from './resolvers/user-resolver';
const createSchema = () =>
buildSchema({
resolvers: [UserResolver],
});
const startApolloServer = async () => {
const schema = await createSchema();
const app = express();
const httpServer = http.createServer(app);
const server = new ApolloServer({
schema,
csrfPrevention: true,
cache: 'bounded',
plugins: [
ApolloServerPluginDrainHttpServer({ httpServer }),
ApolloServerPluginLandingPageLocalDefault({ embed: true }),
],
});
await server.start();
server.applyMiddleware({ app });
httpServer.listen(4000, () => console.log(`🚀 Server ready at http://localhost:4000${server.graphqlPath}`));
};
startApolloServer();
서버를 실행해서 제대로 동작하는지 확인해보기 위해, server/package.json의 scripts를 추가한다.
<typescript />
// server/package.json
{
"name": "@typeorm-apollo-server-example/server",
"version": "0.1.0",
"packageManager": "yarn@3.2.3",
"scripts": {
"dev": "nodemon -w src src/index.ts"
},
...
}
nodemon
: 서버 파일에 변경사항이 있으면 지켜보고 있다가 알아서 서버를 재시작해주는 툴
실행 결과
<shell />yarn server dev



2.5. docker로 postgres 띄우기
편하게 관리하기 위해 docker-compose를 사용하여 postgres 컨테이너를 띄운다.
docker-compose.yml 작성
<python />
# docker-compose.yml
version: '3.4'
services:
gql_test_db:
container_name: "gql_test_db"
image: postgres
ports:
- "5432:5432"
expose:
- "5432"
volumes:
- "./pgdata:/var/lib/postgresql/data"
environment:
- "POSTGRES_DB=gql_test_db"
- "POSTGRES_USER=postgres"
- "POSTGRES_PASSWORD=root"
- "PGDATA=/var/lib/postgresql/data/pgdata"
postgres 컨테이너를 실행한다.
<shell />docker-compose up -d
2.6. postgres와 typeorm, apollo server 연동
이제 실제 DB와 연동해서 조회/삽입을 할 수 있도록 기능을 개선한다. datasource를 구현하기 전에 .env의 환경변수에 DB connect를 하기 위한 정보를 입력한다.
<typescript />
// .env
POSTGRES_HOST=127.0.0.1
POSTGRES_DB=gql_test_db
POSTGRES_PORT=5432
POSTGRES_USER=postgres
POSTGRES_PASSWORD=root
그리고, src/data-source.ts 파일을 만들고 datasource를 생성한다.
<typescript />
// src/data-source.ts
import dotenv from 'dotenv';
import { DataSource } from 'typeorm';
import User from './entities/user';
dotenv.config({ path: '.env' });
const { POSTGRES_HOST, POSTGRES_DB, POSTGRES_PORT, POSTGRES_USER, POSTGRES_PASSWORD } = process.env;
const PostgresDataSource = new DataSource({
type: 'postgres',
host: POSTGRES_HOST,
port: parseInt(POSTGRES_PORT),
username: POSTGRES_USER,
password: POSTGRES_PASSWORD,
database: POSTGRES_DB,
synchronize: true,
logging: true,
entities: [User],
subscribers: [],
migrations: [],
});
export default PostgresDataSource;
그리고 서버가 실행되기 전에 datasource가 초기화 될 수 있도록 index.ts 코드를 수정한다.
<typescript />
// index.ts
// ...
import PostgresDataSource from './data-source';
// ...
const startApolloServer = async () => {
await PostgresDataSource.initialize(); // DataSource 초기화
const schema = await createSchema();
const app = express();
// ...
};
startApolloServer();
여기까지 하면 DB와 연동은 완료! 이제 조회/삽입 기능을 구현해보자
테이블과 entity를 매핑하기 위해 아래와 같이 어노테이션을 추가한다.
<typescript />
// src/entities/user.ts
import { Field, ID, ObjectType } from 'type-graphql';
import { BaseEntity, Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
@Entity()
@ObjectType()
class User extends BaseEntity {
@PrimaryGeneratedColumn('uuid')
@Field(() => ID)
id: string;
@Column({ length: '255' })
@Field(() => String)
email: string;
@Column({ length: '50' })
@Field(() => String)
name: string;
@Column({ length: '255', nullable: true })
@Field(() => String, { nullable: true })
address: string;
}
export default User;
@Entity
를 class에 붙여주면 알아서 class 이름으로 테이블을 생성해줌. 인자로 문자열을 넣어주면 넣어준 문자열로 테이블이 생성@PrimaryGeneratedColumn()
을 이용하면 primary key를 자동으로 생성해줌@Column
을 이용하여 테이블의 column 속성을 정의할 수 있음
그리고 방금 만든 Entity로 DB 조회/삽입을 할 수 있다.
<typescript />
// src/resolvers/user-resolver.ts
import { Arg, Mutation, Query, Resolver } from 'type-graphql';
import User from '../entities/user';
@Resolver()
class UserResolver {
@Query((returns) => User)
async user(@Arg('id') id: string) {
return await User.findOneBy({ id });
}
@Query((returns) => [User])
async users() {
return await User.find();
}
@Mutation((returns) => User)
async createUser(
@Arg('email') email: string,
@Arg('name') name: string,
@Arg('address', { nullable: true }) address?: string
) {
const createdUser = await User.create({
email,
name,
address,
});
await createdUser.save();
return createdUser;
}
}
export default UserResolver;
Java ORM의 JPA와 비슷하게 사용이 가능하다
findOnBy()
: 특정 컬럼을 where 조건으로 넣고 조회할때 사용find()
: 전체 조회create()
&save()
: 값을 insert 하고, commit
서버를 실행하면 테이블이 생성될때 어떻게 생성이 되는지 쿼리를 보여준다.
<typescript />yarn server dev

결과



한가지 더! 콘솔에 SQL 쿼리를 보여주기 때문에 디버깅도 편하다

전체 코드는 아래 저장소 참고
https://github.com/bluemiv/typeorm-apollo-server-example
GitHub - bluemiv/typeorm-apollo-server-example: typeorm을 사용하여 apollo express server 구축 샘플 코드
typeorm을 사용하여 apollo express server 구축 샘플 코드. Contribute to bluemiv/typeorm-apollo-server-example development by creating an account on GitHub.
github.com
3. Reference
'Backend > GraphQL' 카테고리의 다른 글
apollo server와 knex를 이용하여 postgresql 연동하기 (with monorepo, typescript) (0) | 2022.08.26 |
---|---|
yarn berry와 Apollo Server를 이용한 GraphQL 서버 환경 구축 (0) | 2022.05.22 |