본문 바로가기

개발새발 개발자/기타

[TypeGraphQL] 설치 및 resolver, entity 생성하기

1. 필요한 라이브러리 설치

graphql, express, typescript 등을 이용해 간단한 서버를 구축해봅시다. TypeGraphQL은 graphql 사용을 도와주는 라이브러리예요.

yarn init -y

# dependencies
yarn add apollo-server-express express graphql reflect-metadata type-graphql

# dev dependencies
yarn add -D @types/express @types/graphql @types/node nodemon ts-node typescript

터미널에 위와 같이 입력해 설치합니다.

 

여기서 잠깐, dependencies와 devDependencies의 차이를 짚고 넘어갈게요.

dependencies

  • 라이브러리가 컴파일 뿐만 아니라 런타임에도 계속 사용될 때 사용
  • 배포용 패키지(실제 상품으로 사용할 패키지)일 때 사용
  • 해당 패키지에 의존하는 다른 프로젝트를 구동시키기 위한 의존성
  • 즉, 해당 패키지를 사용할 때 필요한 의존성을 명시함
  • npm install --save 명령을 하면 프로젝트 정보가 저장됨

devDependencies

  • 라이브러리가 프로젝트의 컴파일(빌드) 타임에 필요할 때 사용
  • 목, 테스트 패키지 등 개발용일 때 사용
  • 개발, 테스트가 아니라 활용만 하려는 목적일 때는 제외
  • 테스트하거나 개발에 필요한 패키지를 명시함
  • npm install --save -dev 명령을 하면 프로젝트 정보가 저장됨

그렇다면 devDependencies에 설치한 @types는 무엇을 의미할까요?

@types

  • 타입스크립트에 내장된 type definition 시스템

이를 통해 타입스크립트에서 정확하게 타입을 체크할 수 있는 것 같아요.

2. 기본 세팅

index.js

import {ApolloServer} from "apollo-server-express";
import * as Express from "express";
import {buildSchema, Query, Resolver} from "type-graphql";
import "reflect-metadata";
import {createConnection} from "typeorm";

@Resolver()
class HelloResolver {
    // resolver가 리턴하는 값. 대문자 String은 클래스를 의미한다.
    @Query(() => String, {name: 'helloWorld'})
    // 함수의 이름이 쿼리의 이름이 된다. playground에서 hello를 요청하면 값을 리턴한다.
    async hello() {
        return "hello world!";
    }
}

const main = async () => {
    const schema = await buildSchema({
        resolvers: [HelloResolver],
    });
    const apolloServer = new ApolloServer({schema});

    const app = Express();

    apolloServer.applyMiddleware({app});

    app.listen(4000, () => {
        console.log('server started on http://localhost:4000/graphql');
    })
};

main();

코드 작성 후 터미널에서 아래의 명령어로 서버를 실행해볼게요.

yarn start

그럼 http://localhost:4000/graphql에 접속할 수 있습니다. graphql 기반의 요청, 응답을 테스트해볼 수 있는 곳이에요.

 

위에서 우리는 'hello'라는 쿼리를 만들었으니까 hello로 요청하면 리턴값인 'hello world'가 출력됩니다!

 

@Resolver()
class HelloResolver {
    // options에 name으로 함수 이름을 따로 지정할 수 있다.
    @Query(() => String, {name: 'helloWorld'})
    async hello() {
        return "hello world!";
    }
}

옵션으로 쿼리 이름을 따로 지정해줄 수도 있어요. 위처럼 선언한다면

 

이번엔 helloWorld로 요청해 결과를 받을 수 있어요.

 

@Resolver()
class HelloResolver {
    @Query(() => String, {nullable = true})
    async hello() {
        return "hello world!";
    }
}

이렇게 값에 null이 가능하도록 만들 수도 있습니다. 이밖에도 다양한 옵션을 활용할 수 있어요.

 

3. TypeORM 설치

이번엔 TypeORM을 설치해봅시다. 

yarn add pg typeorm bcryptjs

yarn add -D @types/bcryptjs

bcriptjs는 나중에 로그인 암호화에 사용할 라이브러리입니다.

 

ormconfig.json

{
  "name": "default",
  "type": "postgres",
  "host": "localhost",
  "port": "5432",
  "username": "postgres",
  "password": "postgres",
  "database": "typegraphql-example",
  "synchronize": true,
  "logging": true,
  "entities": [
    "src/entity/*.*"
  ]
}

데이터베이스 설정 파일을 만들어 준 후,

 

const main = async () => {
    // ormconfig.json을 읽어들인다.
    await createConnection();

    const schema = await buildSchema({
        resolvers: [HelloResolver],
    });
    const apolloServer = new ApolloServer({schema});

    const app = Express();

    apolloServer.applyMiddleware({app});

    app.listen(4000, () => {
        console.log('server started on http://localhost:4000/graphql');
    })
};

이렇게 커넥션을 생성하면 위에서 만든 파일을 불러오게 됩니다.

 

4. 엔티티 생성

entity/User.ts

import {BaseEntity, Column, Entity, PrimaryGeneratedColumn} from "typeorm";

// 엔티티를 정의한다.
@Entity()
export class User extends BaseEntity {
    @PrimaryGeneratedColumn()
    id: number;

    @Column()
    firstName: string;

    @Column()
    lastName: string;

    @Column("text", {unique: true})
    email: string;

    @Column()
    password: string;
}

이제 엔티티를 만들어볼게요. id와 이름, 이메일, 패스워드를 정의해줍니다.

 

여기서 주의할 점은 BaseEntity를 상속해야한다는 거예요. 그래야 create(), update() 처럼 DB를 조작할 수 있는 메서드를 손쉽게 사용할 수 있어요!

 

createdb typegraphql-example

createuser postgres

설정한 대로 database와 유저를 설정합니다.

 

yarn start    

yarn run v1.22.4
$ nodemon --exec ts-node src/index.ts
[nodemon] 2.0.2
[nodemon] to restart at any time, enter `rs`
[nodemon] watching dir(s): *.*
[nodemon] watching extensions: ts,json
[nodemon] starting `ts-node src/index.ts`
query: START TRANSACTION
query: SELECT * FROM current_schema()
query: SELECT * FROM "information_schema"."tables" WHERE ("table_schema" = 'public' AND "table_name" = 'user')
query: SELECT *, ('"' || "udt_schema" || '"."' || "udt_name" || '"')::"regtype" AS "regtype" FROM "information_schema"."columns" WHERE ("table_schema" = 'public' AND "table_name" = 'user')
query: SELECT "ns"."nspname" AS "table_schema", "t"."relname" AS "table_name", "cnst"."conname" AS "constraint_name", pg_get_constraintdef("cnst"."oid") AS "expression", CASE "cnst"."contype" WHEN 'p' THEN 'PRIMARY' WHEN 'u' THEN 'UNIQUE' WHEN 'c' THEN 'CHECK' WHEN 'x' THEN 'EXCLUDE' END AS "constraint_type", "a"."attname" AS "column_name" FROM "pg_constraint" "cnst" INNER JOIN "pg_class" "t" ON "t"."oid" = "cnst"."conrelid" INNER JOIN "pg_namespace" "ns" ON "ns"."oid" = "cnst"."connamespace" LEFT JOIN "pg_attribute" "a" ON "a"."attrelid" = "cnst"."conrelid" AND "a"."attnum" = ANY ("cnst"."conkey") WHERE "t"."relkind" = 'r' AND (("ns"."nspname" = 'public' AND "t"."relname" = 'user'))
query: SELECT "ns"."nspname" AS "table_schema", "t"."relname" AS "table_name", "i"."relname" AS "constraint_name", "a"."attname" AS "column_name", CASE "ix"."indisunique" WHEN 't' THEN 'TRUE' ELSE'FALSE' END AS "is_unique", pg_get_expr("ix"."indpred", "ix"."indrelid") AS "condition", "types"."typname" AS "type_name" FROM "pg_class" "t" INNER JOIN "pg_index" "ix" ON "ix"."indrelid" = "t"."oid" INNER JOIN "pg_attribute" "a" ON "a"."attrelid" = "t"."oid"  AND "a"."attnum" = ANY ("ix"."indkey") INNER JOIN "pg_namespace" "ns" ON "ns"."oid" = "t"."relnamespace" INNER JOIN "pg_class" "i" ON "i"."oid" = "ix"."indexrelid" INNER JOIN "pg_type" "types" ON "types"."oid" = "a"."atttypid" LEFT JOIN "pg_constraint" "cnst" ON "cnst"."conname" = "i"."relname" WHERE "t"."relkind" = 'r' AND "cnst"."contype" IS NULL AND (("ns"."nspname" = 'public' AND "t"."relname" = 'user'))
query: SELECT "con"."conname" AS "constraint_name", "con"."nspname" AS "table_schema", "con"."relname" AS "table_name", "att2"."attname" AS "column_name", "ns"."nspname" AS "referenced_table_schema", "cl"."relname" AS "referenced_table_name", "att"."attname" AS "referenced_column_name", "con"."confdeltype" AS "on_delete", "con"."confupdtype" AS "on_update", "con"."condeferrable" AS "deferrable", "con"."condeferred" AS "deferred" FROM ( SELECT UNNEST ("con1"."conkey") AS "parent", UNNEST ("con1"."confkey") AS "child", "con1"."confrelid", "con1"."conrelid", "con1"."conname", "con1"."contype", "ns"."nspname", "cl"."relname", "con1"."condeferrable", CASE WHEN "con1"."condeferred" THEN 'INITIALLY DEFERRED' ELSE 'INITIALLY IMMEDIATE' END as condeferred, CASE "con1"."confdeltype" WHEN 'a' THEN 'NO ACTION' WHEN 'r' THEN 'RESTRICT' WHEN 'c' THEN 'CASCADE' WHEN 'n' THEN 'SET NULL' WHEN 'd' THEN 'SET DEFAULT' END as "confdeltype", CASE "con1"."confupdtype" WHEN 'a' THEN 'NO ACTION' WHEN 'r' THEN 'RESTRICT' WHEN 'c' THEN 'CASCADE' WHEN 'n' THEN 'SET NULL' WHEN 'd' THEN 'SET DEFAULT' END as "confupdtype" FROM "pg_class" "cl" INNER JOIN "pg_namespace" "ns" ON "cl"."relnamespace" = "ns"."oid" INNER JOIN "pg_constraint" "con1" ON "con1"."conrelid" = "cl"."oid" WHERE "con1"."contype" = 'f' AND (("ns"."nspname" = 'public' AND "cl"."relname" = 'user')) ) "con" INNER JOIN "pg_attribute" "att" ON "att"."attrelid" = "con"."confrelid" AND "att"."attnum" = "con"."child" INNER JOIN "pg_class" "cl" ON "cl"."oid" = "con"."confrelid" INNER JOIN "pg_namespace" "ns" ON "cl"."relnamespace" = "ns"."oid" INNER JOIN "pg_attribute" "att2" ON "att2"."attrelid" = "con"."conrelid" AND "att2"."attnum" = "con"."parent"
query: SELECT * FROM "information_schema"."tables" WHERE "table_schema" = current_schema() AND "table_name" = 'typeorm_metadata'
query: CREATE TABLE "user" ("id" SERIAL NOT NULL, "firstName" character varying NOT NULL, "lastName" character varying NOT NULL, "email" text NOT NULL, "password" character varying NOT NULL, CONSTRAINT "UQ_e12875dfb3b1d92d7d7c5377e22" UNIQUE ("email"), CONSTRAINT "PK_cace4a159ff9f2512dd42373760" PRIMARY KEY ("id"))
query: COMMIT
server started on http://localhost:4000/graphql

다시 서버를 실행했을 때 쿼리가 출력되면 성공입니다 :)