nestjs + graphql + typeorm 실제 구성하기 - resolver 만들기

김가영·2021년 4월 11일
1

Node.js

목록 보기
29/34

자세한 코드는 https://github.com/jujube0/nest-graphql-typeorm

원래 graphql의 기본 코드는 다음과 같다.

const database = require('./database')
const { ApolloServer, gql } = require('apollo-server')

// graphQL 명세에서 사용될 데이터, 요청의 타입을 지정하는 것
// gql(template literal tag) 로 생성된다. 
const typeDefs = gql`
  type Query {
    teams: [Team]
    equipments: [Equipment]
    supplies: [Supply]
  }
  type Team {
    id: Int
    manager: String
    office: String
    extension_number: String
    mascot: String
    cleaning_duty: String
    project: String
  }
  type Equipment {
    id: String
    used_by: String
    count: Int
    new_or_used: String
  }
  type Supply {
    id: String
    team: Int
  }
`
// resolver : 서비스의 액션들을 함수로 지정(데이터를 반환, 입력, 수정, 삭제)
const resolvers = {
  Query: {
    teams: () => database.teams,
    equipments : () => database.equipments,
    supplies : () => database.supplies,
  }
}
const server = new ApolloServer({ typeDefs, resolvers })

server.listen().then(({ url }) => {
console.log(`🚀  Server ready at ${url}`)
})

typeDefs 를 통해 데이터와 요청(Query, Mutation)의 type들을 정의하고,
resolver 는 요청(Query ,Mutation)들의 액션을 실제 구현하는 부분이다. 참고로 graphql 에서 요청은 Query와 Mutation으로 나뉘는데 Query는 CRUD에서 Read를, Mutation은 나머지 요청들을 담당한다.

typeDefs에서 요청이 어떤 params을 받아 어떤 것을 return할 지를 명시해주고, resolver에서 실제 해당 함수를 구현하는 것.

이전 단계에서 이를 code first로 class에서 entity를 정의하면서 typeDefs에서 type을 정의하는 것은 이미 진행하였다. 그렇다면 우리에게 남은 것은 1. 어떤 mutation과 query가 있을 지를 알려주고, 2. 이를 구현하는 것.

code first method에서는 resolver class에서 resolver function과 Query type을 정의할 수 있다.

Resolver 생성하기

  • users.resolver.ts
import { Resolver, Query, Args, Int, ResolveField, Parent } from '@nestjs/graphql';
import { User } from './user.entity';
import { UsersService } from './users.service';

@Resolver(() => User)
export class UsersResolver {
  constructor(private readonly userService: UsersService) {}

  @Query(() => User)
  async user(@Args('id', { type: () => Int }) id: number) {
    return this.userService.findById(id);
  }
}

대략적인 구조는 위와 같다. @Query@Mutation 데코레이터를 이용하여 mutation과 query를 명시해줘야한다. query와 mutation을 만드는 자세한 방법은 아래에서 다루기로 한다. 뒤의 작업들은 user, record, movie entity class 가 모두 생성되어 db에 저장되어 있다는 것을 전제로 하고 진행된다.

Query

@Query(() => User)
  async user(@Args('id', { type: () => Int }) id: number) {
    return this.userService.findById(id);
  }

@Query() 에서 () => User 는 해당 쿼리가 User 객체를 반환함을 명시한다. 만약 array를 반환하기 위해서는 [User] 처럼 사용해주면 된다.

query {
  user(id:1) {
    id
    nickname,
  }
}

실제 playground에서는 다음과 같이 작동시켜주면 된다. 참고로 playground는 주소/graphql 로 접속하면 접근 가능하다.


query를 다음과 같이 정의할 수도 있다. 역시 play ground에서는 user라는 query를 동일하게 이용할 수 있다. 대신 함수 이름을 다르게 바꿔줄 수 있다는 것이 장점

@Query(() => User, { name: 'user' })
  async getUser(@Args('id', { type: () => Int }) id: number) {
    return this.userService.findById(id);
  }

@Args

@Args 는 user query가 받을 변수들을 명시한다. @Args('id', { type: () => Int } 는 id라는 이름으로 Int 변수를 받으라는 것.

사실 string을 그냥 사용할 때에는

@Query(() => [User], { name: 'users' })
  async getUsersByNickname(@Args('nickname') nickname: string) {
    return this.userService.findByUsersByNickname(nickname);
  }

다음과 같이 type option을 지정해주지 않아도 된다. 하지만 Int나 Float경우에는 type이 필요하다. 그 이유는 typescript의 number로는 그 둘을 구분할 수 없기 때문!

다른 옵션으로는 defaultValue, description, nullable 가 있다.

여러개의 @Args를 넣고 싶을 땐 ,로 구분하여 넣어주면 된다. Args가 너무 많아지면 args의 class를 따로 넣어주어 해결 가능하다. (Nest - Dedicated arguments class)

@Mutation(() => User)
  async createUser(@Args('createUserInput') createUserInput: CreateUserInput) {
    return await this.userService.create(createUserInput);
  }
  • createUserInput.ts
import { Field, InputType } from "@nestjs/graphql";

@InputType()
export class CreateUserInput {
  @Field()
  nickname: string;

  @Field()
  password: string;
}

playground에서는

mutation {
  createUser(createUserInput: {
    nickname:"jujube0",
    password: "1234"
  }) {
    id,
    password,
    nickname,
    flag_active
  }
}

mutation도 query와 동일, @Mutation 데코레이터만 추가해주면 된다.

  • users.resolver.ts
import { Resolver, Query, Args, Int, ResolveField, Parent, Mutation } from '@nestjs/graphql';
import { CreateUserInput } from './createUserInput.input';
import { User } from './user.entity';
import { UsersService } from './users.service';

@Resolver(() => User)
export class UsersResolver {
  constructor(private readonly userService: UsersService) {}

  @Query(() => User, { name: 'user' })
  async getUser(@Args('id', { type: () => Int }) id: number) {
    return this.userService.findById(id);
  }

  @Query(() => [User], { name: 'users' })
  async getUsersByNickname() {
    return this.userService.findUsers();
  }

  @Mutation(() => User)
  async createUser(@Args('createUserInput') createUserInput: CreateUserInput) {
    return await this.userService.create(createUserInput);
  }

  @ResolveField()
  async records(@Parent() user: User) {
    return await this.userService.getRecords(user.id);
  }
}

위와 같이 자세한 구현은 service에서 담당하고 resolver에서는 정의만 해주었다. @ResolveField()는 User 객체에 one-to-many로 연결된 record들을 가져오는 방법

profile
개발블로그

0개의 댓글