2. Apollo Server Express Setup

2020-09-24

Apollo Server는 모든 GraphQL 클라이언트와 호환되는 GraphQL 서버다.

yarn add express apollo-server-express graphql type-graphql
yarn add -D @types/express

컴파일 타임에 데코레이터에 메타데이타를 전달하기 위해 reflect-metadata를 설치한다.

yarn add reflect-metadata

types.ts

import { EntityManager, IDatabaseDriver, Connection } from "@mikro-orm/core";
export type MyContext = {
  em: EntityManager<any> & EntityManager<IDatabaseDriver<Connection>>;
};

Entity Manger의 타입인 MyContext를 정의한다.

@Ctx() { em }: MyContext

resolver의 context를 decorate할 때 em의 타입을 알려준다.

resolvers/post.ts

import { MyContext } from "../types";
import { Arg, Ctx, Mutation, Query, Resolver } from "type-graphql";
import { Post } from "../entities/Post";

@Resolver()
export class PostResolver {
  @Mutation(() => Post)
  async createPost(
    @Arg("title") title: string,
    @Ctx() { em }: MyContext
  ): Promise<Post> {
    const post = em.create(Post, { title });
    await em.persistAndFlush(post);
    return post;
  }
}

index.ts

import "reflect-metadata";
import express from "express";
import { ApolloServer } from "apollo-server-express";
import { buildSchema } from "type-graphql";
import { PostResolver } from "./resolvers/post";

const main = async () => {
  const app = express();

  const apolloServer = new ApolloServer({
    schema: await buildSchema({
      resolvers: [PostResolver],
      validate: false,
      /*buildSchema의 validate를 false로 할 시
            자동 validation과 기본 구성 개체 전달을
            사용하지 않는다.*/
    }),
    // req, res는 나중에 사용한다. 미리 추가해두자.
    context: ({ req, res }): MyContext => ({ em: orm.em, req, res }),
    /*
        context 인수는 인증(authentication scope), 데이터베이스 연결,
        사용자 정의 가져오기(custom fetch) 기능 등과 같이
        resolver가 필요로 하는 항목을 전달할 때 유용하다
        */
  });

  apolloServer.applyMiddleware({ app });

  app.listen(4000, () => {
    console.log("server started on localhost:4000");
  });
};

context로 mikro-orm의 orm.em(entity manager)을 resolver에게 넘기고,

post.ts에서 em.create()로 post를 create한다.

캡처2


resolvers/post.ts

@Resolver()
export class PostResolver {
  @Query(() => [Post])
  posts(@Ctx() { em }: MyContext): Promise<Post[]> {
    return em.find(Post, {});
  }

  @Query(() => Post, { nullable: true })
  post(@Arg("id") id: number, @Ctx() { em }: MyContext): Promise<Post | null> {
    return em.findOne(Post, { id });
  }
}

캡처

createPost로 생성한 post를 post(id){}로 조회한다.

posts{}는 생성된 모든 post를 보여준다.


resolvers/post.ts

import { MyContext } from "../types";
import { Arg, Ctx, Mutation, Query, Resolver } from "type-graphql";
import { Post } from "../entities/Post";

@Resolver()
export class PostResolver {
  @Mutation(() => Post, { nullable: true })
  async updatePost(
    @Arg("id") id: number,
    @Arg("title", () => String, { nullable: true }) title: string,
    @Ctx() { em }: MyContext
  ): Promise<Post | null> {
    const post = await em.findOne(Post, { id });
    if (!post) {
      return null;
    }
    if (typeof title !== "undefined") {
      post.title = title;
      await em.persistAndFlush(post);
    }
    return post;
  }

  @Mutation(() => Boolean)
  async deletePost(
    @Arg("id") id: number,
    @Ctx() { em }: MyContext
  ): Promise<boolean> {
    await em.nativeDelete(Post, { id });
    return true;
  }
}

delete와 update 기능을 추가했다.