반응형
반응형

 

📝인증 및 인가

import { ApolloServer } from '@apollo/server';
import { startStandaloneServer } from '@apollo/server/standalone';
import { GraphQLError } from 'graphql';

interface MyContext {
  user: UserInterface;
}

const server = new ApolloServer<MyContext>({
  typeDefs,
  resolvers,
});

const { url } = await startStandaloneServer(server, {
  
  context: async ({ req }) => {
    // get the user token from the headers
    const token = req.headers.authorization || '';

    // try to retrieve a user with the token
    const user = getUser(token);

    // optionally block the user
    // we could also check user roles/permissions here
    if (!user)
      // throwing a `GraphQLError` here allows us to specify an HTTP status code,
      // standard `Error`s will have a 500 status code by default
      throw new GraphQLError('User is not authenticated', {
        extensions: {
          code: 'UNAUTHENTICATED',
          http: { status: 401 },
        },
      });

    // add the user to the context
    return { user };
  },
  
});

console.log(`🚀 Server listening at: ${url}`);

Apollo Server에서는 contextValue라는 걸 제공하는데 헤더에 대한 정보를 추출할 수 있습니다 이걸 이용해 인증 및 인가에 대한 처리가 가능합니다

 

const resolvers = {
  Query: {
    adminData: (parent, args, context) => {
      if (!context.user || context.user.role !== 'ADMIN') {
        throw new AuthenticationError('You must be an admin to view this data');
      }

      return "This is private data only for administrators.";
    }
  }
};

Resolver에서도 가능합니다

 

const typeDefs = `#graphql
  directive @auth(requires: Role = ADMIN) on OBJECT | FIELD_DEFINITION

  enum Role {
    ADMIN
    REVIEWER
    USER
  }

  type User @auth(requires: USER) {
    name: String
    banned: Boolean @auth(requires: ADMIN)
    canPost: Boolean @auth(requires: REVIEWER)
  }
`;

Directive로 설정도 가능합니다

 

자세한 건 공식 문서를 참고바랍니다

https://www.apollographql.com/docs/apollo-server/security/authentication

 

📝CORS

CORS 설정이 기본적으로 되어있다 
자세한 건 공식 문서를 참고바랍니다

https://www.apollographql.com/docs/apollo-server/security/cors

 

📝SSL

import { ApolloServer } from '@apollo/server';
import { expressMiddleware } from '@apollo/server/express4';
import { ApolloServerPluginDrainHttpServer } from '@apollo/server/plugin/drainHttpServer';
import typeDefs from './graphql/schema';
import resolvers from './graphql/resolvers';
import cors from 'cors';
import express from 'express';
import http from 'http';
import https from 'https';
import fs from 'fs';

const configurations = {
  // Note: You may need sudo to run on port 443
  production: { ssl: true, port: 443, hostname: 'example.com' },
  development: { ssl: false, port: 4000, hostname: 'localhost' },
};

const environment = process.env.NODE_ENV || 'production';
const config = configurations[environment];

const server = new ApolloServer({
  typeDefs,
  resolvers,
});
await server.start();

const app = express();
// our express server is mounted at /graphql
app.use('/graphql', cors<cors.CorsRequest>(), express.json(), expressMiddleware(server));

// Create the HTTPS or HTTP server, per configuration
let httpServer;
if (config.ssl) {
  // Assumes certificates are in a .ssl folder off of the package root.
  // Make sure these files are secured.
  httpServer = https.createServer(
    {
      key: fs.readFileSync(`./ssl/${environment}/server.key`),
      cert: fs.readFileSync(`./ssl/${environment}/server.crt`),
    },

    app,
  );
} else {
  httpServer = http.createServer(app);
}

await new Promise<void>((resolve) => httpServer.listen({ port: config.port }, resolve));

console.log('🚀 Server ready at', `http${config.ssl ? 's' : ''}://${config.hostname}:${config.port}/graphql`);

 

SSL 설정을 프로뎍선 레벨과 개발 레벨로 나눌 수 있다

 

자세한 건 공식 문서를 참고바랍니다
https://www.apollographql.com/docs/apollo-server/security/terminating-ssl

 

📝Proxy (프록시)

나가는 요청등에 대한 프록시 설정이 가능한데 자세한 건 문서를 참고바랍니다
https://www.apollographql.com/docs/apollo-server/security/proxy-configuration

 

📝클라우드 서버 배포 (Lambda, Heroku)

클라우드 서버에 가장 많이 배포하는 유형에 대한 설명이 있습니다 AWS의 Lambda와 Heroku가 있으니 자세한건 문서 참고 바랍니다
https://www.apollographql.com/docs/apollo-server/deployment/lambda
https://www.apollographql.com/docs/apollo-server/deployment/heroku

 

📝로깅 (Logging)

GRAPH OS라는 제품을 제공하는데 이걸 이용해 로깅을 따로 집계하고 통계를 내는 등에 역할을 도와줍니다

 

const myPlugin = {
  // Fires whenever a GraphQL request is received from a client.
  async requestDidStart(requestContext) {
    console.log('Request started! Query:\n' + requestContext.request.query);

    return {
      // Fires whenever Apollo Server will parse a GraphQL
      // request to create its associated document AST.
      async parsingDidStart(requestContext) {
        console.log('Parsing started!');
      },

      // Fires whenever Apollo Server will validate a
      // request's document AST against your GraphQL schema.
      async validationDidStart(requestContext) {
        console.log('Validation started!');
      },
    };
  },
};

const server = new ApolloServer({
  typeDefs,
  resolvers,
  plugins: [myPlugin],
});

또한 사용자 정의 플러그인을 이용해 세분화된 작업 로깅을 설정할 수 있습니다

자세한 사항은 공식 문서를 참고바랍니다
https://www.apollographql.com/docs/apollo-server/monitoring/metrics

반응형
반응형

📝캐싱

enum CacheControlScope {
  PUBLIC
  PRIVATE
}

directive @cacheControl(
  maxAge: Int
  scope: CacheControlScope
  inheritMaxAge: Boolean
) on FIELD_DEFINITION | OBJECT | INTERFACE | UNION


/** 예제 (필드 캐싱) **/
type Post {
  id: ID!
  title: String
  author: Author
  votes: Int @cacheControl(maxAge: 30)
  comments: [Comment]
  readByCurrentUser: Boolean! @cacheControl(maxAge: 10, scope: PRIVATE)
}

/** 예제 (타입 캐싱) **/
type Post @cacheControl(maxAge: 240) {
  id: Int!
  title: String
  author: Author
  votes: Int
  comments: [Comment]
  readByCurrentUser: Boolean!
}

/** 우선 캐싱순위 필드 우선 [상세한 거 우선] **/
type Comment {
  post: Post! # Cached for up to 240 seconds
  body: String!
}

type Comment {
  post: Post! @cacheControl(maxAge: 120)
  body: String!
}

지시어를 추가해야 사용할 수 있다

 

maxAge cache 유지 기간로 Default값은 0이다
scope cache 유지 범위 PUBLIC이 Default값이며 Private의 경우 단일 사용자에 대한 캐시 유지 범위이다
inheritMaxAge 상위 필드에 maxAge가 있는 경우 그걸 상속받아서 사용한다

 

 

📝 리졸버에서 캐싱 사용하기

/** cacheControl.setCacheHint로 캐싱 설정하기 **/
import { cacheControlFromInfo } from '@apollo/cache-control-types';

const resolvers = {
  Query: {
    post: (_, { id }, _, info) => {
      // Access ApolloServerPluginCacheControl's extension of the GraphQLResolveInfo object
      const cacheControl = cacheControlFromInfo(info)
      cacheControl.setCacheHint({ maxAge: 60, scope: 'PRIVATE' });
      return find(posts, { id });
    },
  },
};

/** cacheControl.cacheHint 필드의 현재 캐시 힌트 확인 **/
import { cacheControlFromInfo } from '@apollo/cache-control-types';

const resolvers = {
  Query: {
    post: (_, { id }, _, info) => {
      // Access ApolloServerPluginCacheControl's extension of the GraphQLResolveInfo object
      const cacheControl = cacheControlFromInfo(info)
      cacheControl.setCacheHint({ maxAge: 60, scope: 'PRIVATE' });
      return find(posts, { id });
    },
  },
};

 

 

📝 플러그인 캐싱 (커스텀 캐싱)

new ApolloServer({
  plugins: [
    ApolloServerPluginCacheControl({ calculateHttpHeaders: false }), // 아폴로 서버 캐싱 헤더 설정 중지
    {
      async requestDidStart() {
        return {
          async willSendResponse(requestContext) {
            // 캐싱 커스텀 설정
            const { response, overallCachePolicy } = requestContext;
            const policyIfCacheable = overallCachePolicy.policyIfCacheable();
            if (policyIfCacheable && !response.headers && response.http) {
              response.http.headers.set(
                'cache-control',
                // ... or the values your CDN recommends
                `max-age=0, s-maxage=${
                  overallCachePolicy.maxAge
                }, ${policyIfCacheable.scope.toLowerCase()}`,
              );
            }
          },
        };
      },
    },
  ],
});

직접 플러그인에다가 캐싱을 커스텀하는 방법도 존재하며 메모리 미들웨어인 Redis등과 같이 사용할 수도 있습니다

 

자세한 건 캐시 인메모리 구현에 대해서 참고 바랍니다

https://www.apollographql.com/docs/apollo-server/performance/caching

https://www.apollographql.com/docs/apollo-server/performance/cache-backends/

 

 

일반적으로 스키마의 모든 필드 에 캐시 힌트를 지정할 필요는 없다네요 성능에 대해서 개선시킬 가능성이 있을 경우에 이용하면 좋을 거 같습니다

 

 

반응형
반응형

📝Data Fetch

아폴로 서버에서 데이터를 Fetch할 수 있는 라이브러리를 제공한다

 

const sqlite3 = require("sqlite3").verbose();
const { SQLDataSource } = require("datasource-sql");

class DogsDataSource extends SQLDataSource {
  constructor({ cache, token }) {
    const db = new sqlite3.Database("dogs.db");
    super({ db });
    this.token = token; // 혹은 다른 인증 메커니즘을 사용
  }

  async getDogs() {
    return this.db.all("SELECT * FROM dogs");
  }

  async getDogById(id) {
    return this.db.get("SELECT * FROM dogs WHERE id = ?", id);
  }
}


import { RESTDataSource } from '@apollo/datasource-rest';

class MoviesAPI extends RESTDataSource {
  override baseURL = 'https://movies-api.example.com/';

  async getMovie(id: string): Promise<Movie> {
    return this.get<Movie>(`movies/${encodeURIComponent(id)}`);
  }

  async getMostViewedMovies(limit = '10'): Promise<Movie[]> {
    const data = await this.get('movies', {
      params: {
        per_page: limit.toString(), // all params entries should be strings,
        order_by: 'most_viewed',
      },
    });
    return data.results;
  }
}




//highlight-start
interface ContextValue {
  dataSources: {
    dogsDB: DogsDataSource;
    catsApi: CatsAPI;
  };
  token: string;
}
//highlight-end

const server = new ApolloServer<ContextValue>({
  typeDefs,
  resolvers,
});

const { url } = await startStandaloneServer(server, {
  context: async ({ req }) => {
    const { cache } = server;
    const token = req.headers.token;
    return {
      // We create new instances of our data sources with each request.
      // We can pass in our server's cache, contextValue, or any other
      // info our data sources require.
      
      dataSources: {
        dogsDB: new MoviesAPI({ cache, token }),
        catsApi: new CatsAPI({ cache }),
      },
      //highlight-end
      token,
    };
  },
});

console.log(`🚀  Server ready at ${url}`);

SQLDBSource의 예제이며 Apollo Server에 dataSources에 만들어둔 DB Source를 연결하면 된다

API 또한 비슷한 형식으로 하면 된다

이러한 Source들에게 캐싱 일괄처리 등 다양한 옵션들을 제공한다

 

자세한 사항은 공식 문서를 참고하면 좋다

https://www.apollographql.com/docs/apollo-server/data/fetching-data/

https://www.apollographql.com/docs/apollo-server/data/fetching-rest

 

📝웹 프레임워크

기본적으로 startStandaloneServer와 expressMiddleware를 제공합니다 그리고 기본적으로 Apollo Server4에서는 Express 통합인 startStandaloneServer를 사용합니다

 

다양한 웹 프레임워크에서 사용할 수 있게 제공하는 라이브러리가 있다

 

자세한건 공식 문서를 참고하자

https://www.apollographql.com/docs/apollo-server/integrations/integration-index

 

📝Sandbox Landing Page (개발 / 운영)

프로덕션 환경에서는 다른 랜딩 페이지를 제공한다 이는 부분적으로 프로덕션에서 자체 검사를 비활성화하기 때문이다 이말은 Apollo Sandbox와 같은 도구가 작동하지 않는 걸 의미한다

또한 Landing Page를 커스텀하거나 비활성화 할 수도 있다

 

자세한 건 공식 문서를 참고하자

https://www.apollographql.com/docs/apollo-server/workflow/build-run-queries/#custom-landing-page

 

📝Codegen

# This configuration file tells GraphQL Code Generator how
# to generate types based on our schema.
schema: "./schema.graphql"
generates:
  # Specify where our generated types should live.
  ./src/__generated__/resolvers-types.ts:
    plugins:
      - "typescript"
      - "typescript-resolvers"
    config:
      useIndexSignature: true
      # More on this below!
      contextType: "../index#MyContext"

Codegen은 Schema기반으로 그에 맞는 타입스크립트 코드나 리졸버 등을 자동적으로 만들어주는 역할을 해줍니다

다양한 plugins와 config 설정이 존재합니다

 

자세한 건 공식문서인 Graphql-Codegen을 참고해주세요

https://the-guild.dev/graphql/codegen

 

type User {
   id: ID!
   name: String!
   email: String
}

type Query {
   getUser(id: ID!): User
}

export interface User {
   id: string;
   name: string;
   email?: string;
}

export interface Query {
   getUser: User;
}

위에 코드는 해당 스키마를 기준으로 interface가 나온 형태입니다

 

📝Mocking & 테스트 코드

// For clarity in this example we included our typeDefs and resolvers above our test,
// but in a real world situation you'd be importing these in from different files
const typeDefs = `#graphql
  type Query {
    hello(name: String): String!
  }
`;

const resolvers = {
  Query: {
    hello: (_, { name }) => `Hello ${name}!`,
  },
};

it('returns hello with the provided name', async () => {
  const testServer = new ApolloServer({
    typeDefs,
    resolvers,
  });

  const response = await testServer.executeOperation({
    query: 'query SayHelloWorld($name: String) { hello(name: $name) }',
    variables: { name: 'world' },
  });

  // Note the use of Node's assert rather than Jest's expect; if using
  // TypeScript, `assert`` will appropriately narrow the type of `body`
  // and `expect` will not.
  assert(response.body.kind === 'single');
  expect(response.body.singleResult.errors).toBeUndefined();
  expect(response.body.singleResult.data?.hello).toBe('Hello world!');
});

 

executeOperation을 이용해 해당 쿼리에 어떤 파라미터를 줬을 때 결과값에 대한 테스트 코드를 작성할 수 있습니다

또한 Mocking기능도 지원합니다

 

자세한 건 공식문서 참고 바랍니다

https://www.apollographql.com/docs/apollo-server/testing/testing/

https://www.apollographql.com/docs/apollo-server/testing/mocking/

반응형
반응형

 

 

Graphql Schema에서 정의하는 유형은 아래가 전부입니다

  • Scalar
  • Object
  • Input
  • Enum
  • Union
  • Interface

 

📝Scalar

기본 타입들이 정의되어있습니다

  • Int
    • 부호 있는 32비트 정수
  • Float
    • 부호 있는 배정밀도 부동 소수점 값
  • String
    • UTF‐8 문자 시퀀스
  • Boolean
    • true또는false
  • ID
    • 객체를 다시 가져오는 데 자주 사용되거나 캐시의 키로 사용되는 고유 식별자입니다

 

그 외에는 커스텀으로 만들 수 있는데 자세한건 아래 링크를 참조하기 바랍니다

참고로 npm에 있는 bigInt, Decimal등은 공식적으로 지원하는 게 아닌 어떤 사람이 만든 것입니다

https://www.apollographql.com/docs/apollo-server/schema/custom-scalars/

 

 

 

📝Object

Object Type은 스키마에서 정의하는 대부분이다 API요청을 위한 API이름과 반환값 또는 Scalar들로 이루어진 새로운 타입을 정의합니다

type Author {
  name: String
  books: [Book] # []으로 리스트 정의
}

type Query {
  books: [Book]
  authors: [Author]
}
type Mutation {
  addBook(title: String, author: String): Book
}
type Subscription {
  postCreated: Post
}

 

 

 

📝Input

Object Type에 파라미터가 필요한 경우 Scalar타입으로 다 적을수도 있지만 따로 분리 후 Input Type을 만들어서 따로 관리 및 재사용할 수 있습니다

input BlogPostContent {
  title: String
  body: String
}
# Input 사용 예제
type Mutation {
  createBlogPost(content: BlogPostContent!): Post
  updateBlogPost(id: ID!, content: BlogPostContent!): Post
}

 

 

📝Enum

말 그대로 Enum Type이다

enum AllowedColor {
  RED
  GREEN
  BLUE
}

 

📝Union

Union은 정의한 것만 허용한다라는 의미입니다

union SearchResult = Book | Author

type Book {
  title: String!
}

type Author {
  name: String!
}

type Query {
  search(contains: String): [SearchResult!]
}

위에 코드를 보면 search에 대한 Query는 반환 값이 Book Type이던가 Author Type이여야합니다

근데 문제점이 생기는데 search는 어떤 타입을 반환할지 몰라 어떤 필드를 요청할지 모릅니다 이에 대한 해결법은 아래와 같습니다

 

방법1

query GetSearchResults {
  search(contains: "Shakespeare") {
    __typename
    ... on Book {
      title
    }
    ... on Author {
      name
    }
  }
}

Book일 경우 title을 반환하고 Author일 경우 name을 반환합니다

 

방법2 (resolverType)

const resolvers = {
  SearchResult: {
    __resolveType(obj, contextValue, info){
      // Only Author has a name field
      if(obj.name){
        return 'Author';
      }
      // Only Book has a title field
      if(obj.title){
        return 'Book';
      }
      return null; // GraphQLError is thrown
    },
  },
  Query: {
    search: () => { ... }
  },
};

const server = new ApolloServer({
  typeDefs,
  resolvers,
});

const { url } = await startStandaloneServer(server);

console.log(`🚀  Server ready at: ${url}`);

resolver에서는 이런식으로 처리합니

 

📝Interface

interface는 Type과 유사한데 상속받을 수 있습니다

interface Book {
  title: String!
  author: Author!
}

type Textbook implements Book {
  title: String! # Must be present
  author: Author! # Must be present
  courses: [Course!]!
}

union과 동일하게 interface를 반환타입으로 가지는 경우에도 추가적인 필드에 대해서 어떤 걸 가질지 모른다 이에 대한 처리 방법은 아래와 같다

 

방법1

query GetBooks {
  books {
    __typename
    title
    ... on Textbook {
      courses {
        # Only present in Textbook
        name
      }
    }
    ... on ColoringBook {
      colors # Only present in ColoringBook
    }
  }
}

 

방법2 (resolverType)

const resolvers = {
  Book: {
    __resolveType(book, contextValue, info){
      // Only Textbook has a courses field
      if(book.courses){
        return 'Textbook';
      }
      // Only ColoringBook has a colors field
      if(book.colors){
        return 'ColoringBook';
      }
      return null; // GraphQLError is thrown
    },
  },
  Query: {
    books: () => { ... }
  },
};

 

📝Directives

type ExampleType {
  oldField: String @deprecated(reason: "Use `newField`.")
  newField: String
}

@deprecated로 사용 금지에 대한 기능과 문서를 작성할 수 있고 필요한 경우 사용자 정의 지시어를 만들 수 있다

 

https://www.apollographql.com/docs/apollo-server/schema/directives#custom-directives

반응형
반응형

Apollo Server v4에서는 일반적으로 에러 코드도200 메세지를 준다 이거를 옵션을 줘서 200코드 값이 아닌 400등으로  변경할 수 있다 또한 StackTrace 기능도 제공한다

 

📝Apollo Server에서 기본적 제공하는 에러 종류

에러 설명
GRAPHQL_PARSE_FAILED
GraphQL 작업 문자열  구문 오류가 있습니다.
GRAPHQL_VALIDATION_FAILED
GraphQL 작업은 서버 의 스키마에 대해 유효하지 않습니다.
BAD_USER_INPUT
GraphQL 작업에 필드 인수  대한 잘못된 값이 포함되어 있습니다 .
PERSISTED_QUERY_NOT_FOUND
클라이언트는 다음 을 통해 실행할 쿼리 문자열의 해시를 보냈습니다.
, 그러나 쿼리가 APQ 캐시 에 없었습니다 .
PERSISTED_QUERY_NOT_SUPPORTED
클라이언트는 다음 을 통해 실행할 쿼리 문자열의 해시를 보냈습니다.
하지만 서버가 APQ를 비활성화했습니다 .
OPERATION_RESOLUTION_FAILURE
요청이 성공적으로 구문 분석되었으며 서버의 스키마에 대해 유효하지만 서버가 실행할 작업 을 확인할 수 없습니다.
이는 여러 명명된 작업이 포함된 요청이 실행할 작업(예: operationName)을 지정하지 않거나 명명된 작업이 요청에 포함되지 않은 경우에 발생합니다.
BAD_REQUEST
서버가 주어진 GraphQL 작업 을 구문 분석하기 전에 오류가 발생했습니다 .
INTERNAL_SERVER_ERROR
지정되지 않은 오류가 발생했습니다.
Apollo Server가 응답에서 오류 형식을 지정할 때 다른 코드가 설정되지 않은 경우 코드 확장을 이 값으로 설정합니다.

 

📝커스텀 에러

import { GraphQLError } from 'graphql';

throw new GraphQLError('You are not authorized to perform this action.', {
  extensions: {
    code: 'FORBIDDEN',
    argumentName: 'id',
  },
});

 

 

 

📝공통 에러 처리

import { ApolloServer } from '@apollo/server';
import { startStandaloneServer } from '@apollo/server/standalone';
import { ApolloServerErrorCode } from '@apollo/server/errors';

const server = new ApolloServer({
  typeDefs,
  resolvers,
  formatError: (formattedError, error) => {
    // Return a different error message
    if (
      formattedError.extensions.code ===
      ApolloServerErrorCode.GRAPHQL_VALIDATION_FAILED
    ) {
      return {
        ...formattedError,
        message: "Your query doesn't match the schema. Try double-checking it!",
      };
    }

    // Otherwise return the formatted error. This error can also
    // be manipulated in other ways, as long as it's returned.
    return formattedError;
  },
});

const { url } = await startStandaloneServer(server);
console.log(`🚀 Server listening at: ${url}`);

 

 

formatError을 통해 전체 에러에 대한 핸들링이 가능합니다

 

📝에러 리포팅 기능

formatError: (formattedError, error) => {
    if (error instanceof CustomDBError) {
      // do something specific
    }
  },
  
다른 거에 대한 것도 가능

버그에 대한 내용을 가지고 리포팅 등의 기능도 제공합니다

 

📝Subscription (웹소켓)

type Subscription {
  postCreated: Post
}

웹소켓에 대한 API 명세를 먼저 정의하고 따로 Graphql Websocket을 package 관리도구로 설치해 공식문서에 가르쳐준 대로 사용합니다

 

제가 정리한건 큼지막한 내용이 있을 뿐 자세한 옵션들은 공식문서 참고바랍니다

 

 

🔗 참고 및 출처

https://www.apollographql.com/docs/apollo-server/data/errors

https://www.apollographql.com/docs/apollo-server/data/subscriptions

반응형
반응형

📝Apollo Server

  • GraphQL코드를 효율적으로 관리하는데 도움을 줍니다
  • GraphQL의 Query, Type 등 문서화하는데 도움을 줍니다
  • Sandbox를 제공해 직접 GraphQL 테스트도 가능합니다
  • 최적화 기능도 제공합니다

 

Graphql을 개발한 Meta에서 만든 Relay라는게 있지만 학습비용이 높고 React계열만 지원한다는 단점이 존재한다 그에 비해 Apollo는 유연하고 러닝커브가 높지 않다

 

📝리졸버

Apollo Server가 Graphql 작업 처리하는 방법입니다

요청이 들어오면 그에 따라 비즈니스 로직을 처리해 결과를 반환해주는 역할입니다

 

import { ApolloServer } from '@apollo/server';
import { startStandaloneServer } from '@apollo/server/standalone';

// Hardcoded data store
const books = [
  {
    title: 'The Awakening',
    author: 'Kate Chopin',
  },
  {
    title: 'City of Glass',
    author: 'Paul Auster',
  },
];

// Schema definition
const typeDefs = `#graphql
  type Book {
    title: String
    author: String
  }

  type Query {
    books: [Book]
  }
`;

// Resolver map
const resolvers = {
  Query: {
    books() {
      return books;
    },
  },
};

// Pass schema definition and resolvers to the
// ApolloServer constructor
const server = new ApolloServer({
  typeDefs,
  resolvers,
});

// Launch the server
const { url } = await startStandaloneServer(server);
console.log(`🚀 Server listening at: ${url}`);

위에 예시 코드의 경우 resolvers에서 books Query에 대한 리턴을 정의합니다 그 이후에 server에 스키마와 resolver를 등록합니다

 

📝리졸버 체이닝

import { ApolloServer } from '@apollo/server';
import { startStandaloneServer } from '@apollo/server/standalone';

const libraries = [
  {
    branch: 'downtown',
  },
  {
    branch: 'riverside',
  },
];

// The branch field of a book indicates which library has it in stock
const books = [
  {
    title: 'The Awakening',
    author: 'Kate Chopin',
    branch: 'riverside',
  },
  {
    title: 'City of Glass',
    author: 'Paul Auster',
    branch: 'downtown',
  },
];

// Schema definition
const typeDefs = `#graphql
  # A library has a branch and books
  type Library {
    branch: String!
    books: [Book!]
  }

  # A book has a title and author
  type Book {
    title: String!
    author: Author!
  }

  # An author has a name
  type Author {
    name: String!
  }

  # Queries can fetch a list of libraries
  type Query {
    libraries: [Library]
  }
`;

// Resolver map
const resolvers = {
  Query: {
    libraries() {
      // Return our hardcoded array of libraries
      return libraries;
    },
  },
  Library: {
    books(parent) {
      // Filter the hardcoded array of books to only include
      // books that are located at the correct branch
      return books.filter((book) => book.branch === parent.branch);
    },
  },
  Book: {
    // The parent resolver (Library.books) returns an object with the
    // author's name in the "author" field. Return a JSON object containing
    // the name, because this field expects an object.
    author(parent) {
      return {
        name: parent.author,
      };
    },
  },

  // Because Book.author returns an object with a "name" field,
  // Apollo Server's default resolver for Author.name will work.
  // We don't need to define one.
};

// Pass schema definition and resolvers to the
// ApolloServer constructor
const server = new ApolloServer({
  typeDefs,
  resolvers,
});

// Launch the server
const { url } = await startStandaloneServer(server);
console.log(`🚀 Server listening at: ${url}`);


// 호출
query GetBooksByLibrary {
  libraries {
    books {
      title
      author {
        name
      }
    }
  }
}

리졸버 체이닝이란 리졸버의 결과가 다음 리졸버의 입력으로 연결되는 패턴입니다

 

위 예시 코드에 대해 설명하면 Query Libraries에 대한 정의를 하고 해당 리턴의 타입인 [Library]의 값 받습니다 그 이후 결과값을 받아 Library 타입 안에 속하는 books의 타입인 [Book]의 값을 정의합니다

이렇게 이어받아서 작업하는 걸 리졸버 체이닝이라고합니다

 

아래는 리졸버 체이닝에 쓰이는 인자값 입니다

parent 이전 리졸버의 반환값입니다.
args  필드 에 제공된 모든 GraphQL 인수를 포함하는 객체입니다 .
예를 들어 를 실행할 때 리졸버에 전달되는 객체 query{ user(id: "4") } 입니다. argsuser { "id": "4" }
contextValue 특정 작업을 실행하는 모든 확인자 간에 공유되는 개체입니다.
이를 사용하여 인증 정보, 데이터로더 인스턴스 및 확인자 전체에서 추적할 기타 항목을 포함하여 작업별 상태를 공유합니다.

info 필드 이름, 루트에서 필드까지의 경로 등을 포함하여 작업 실행 상태 에 대한 정보가 포함되어 있습니다 .

 

 

contextValue 예시

import { GraphQLError } from 'graphql';

const resolvers = {
  Query: {
    // Example resolver
    adminExample: (parent, args, contextValue, info) => {
      if (contextValue.authScope !== ADMIN) {
        throw new GraphQLError('not admin!', {
          extensions: { code: 'UNAUTHENTICATED' },
        });
      }
    },
  },
};

interface MyContext {
  // You can optionally create a TS interface to set up types
  // for your contextValue
  authScope?: String;
}

const server = new ApolloServer<MyContext>({
  typeDefs,
  resolvers,
});

const { url } = await startStandaloneServer(server, {
  // Your async context function should async and
  // return an object
  
  context: async ({ req, res }) => ({
    authScope: getScope(req.headers.authorization),
  }),
  
});

 

contextValue 예시 2

context: async () => ({
  db: await client.connect(),
})

// Resolver
(parent, args, contextValue, info) => {
  return contextValue.db.query('SELECT * FROM table_name');
}

 

contextValue 예시 3

import { AnimalAPI } from './datasources/animals';

const resolvers = {
  Query: {
    // All of our resolvers can access our shared contextValue!
    dogs: (_, __, contextValue) => {
      return contextValue.dataSources.animalApi.getDogs();
    },
    cats: (_, __, contextValue) => {
      return contextValue.dataSources.animalApi.getCats();
    },
  },
};

interface MyContext {
  // Context typing
  dataSources: {
    animalApi: AnimalAPI;
  };
}

const server = new ApolloServer<MyContext>({
  typeDefs,
  resolvers,
});

const { url } = await startStandaloneServer(server, {
  context: async () => {
    const animalApi = new AnimalAPI();
    return {
      dataSources: {
        animalApi,
      },
    };
  },
});

contextValue통해 공유 객체에 액세스할 수 있습니다

 

 

 

 

반응형
반응형

 

📝Apollo Server로 시작하기

Apollo Server 공식 문서와 제가 개인적으로 연습에 필요한 패키지로 진행했습니다

귀찮아서 ts가 아니라 js로 진행했다는 점 참고바랍니다

 

 

1. 프로젝트 폴더 만들기

mkdir graphql-server-example
cd graphql-server-example

 

2. Node 패키지 설치 및 셋팅

npm init --yes && npm pkg set type="module"

 

3. 필요 패키지 설치

npm install apollo-server graphql
npm install nodemon -D

 

4. 실행 파일 생성 (src/index.js)

import { ApolloServer } from '@apollo/server';
import { startStandaloneServer } from '@apollo/server/standalone';

// A schema is a collection of type definitions (hence "typeDefs")
// that together define the "shape" of queries that are executed against
// your data.

const books = [
    {
        title: 'The Awakening',
        author: 'Kate Chopin',
    },
    {
        title: 'City of Glass',
        author: 'Paul Auster',
    },
];

// Resolvers define how to fetch the types defined in your schema.
// This resolver retrieves books from the "books" array above.
const resolvers = {
    Query: {
        books: () => books,
    },
};

const typeDefs = `#graphql
# Comments in GraphQL strings (such as this one) start with the hash (#) symbol.

# This "Book" type defines the queryable fields for every book in our data source.
type Book {
    title: String
    author: String
}

# The "Query" type is special: it lists all of the available queries that
# clients can execute, along with the return type for each. In this
# case, the "books" query returns an array of zero or more Books (defined above).
type Query {
    books: [Book]
}
`;

// The ApolloServer constructor requires two parameters: your schema
// definition and your set of resolvers.
const server = new ApolloServer({
    typeDefs,
    resolvers,
});

// Passing an ApolloServer instance to the `startStandaloneServer` function:
//  1. creates an Express app
//  2. installs your ApolloServer instance as middleware
//  3. prepares your app to handle incoming requests
const { url } = await startStandaloneServer(server, {
    listen: { port: 4000 },
});

console.log(`🚀  Server ready at: ${url}`);

 

5. 실행 스크립트 작성 (package.json)

{
  "dependencies": {
    "@apollo/server": "^4.10.2",
    "apollo-server": "^3.13.0",
    "graphql": "^16.8.1"
  },
  "devDependencies": {
    "nodemon": "^3.1.0"
  },
  "name": "graphql-server-example",
  "version": "1.0.0",
  "main": "src/index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "nodemon src/index.js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "description": "",
  "type": "module"
}

 

6. npm start로 시작

localhost:4000/grpahql로 apollo server 접속

 

 

https://www.apollographql.com/docs/apollo-server/getting-started/

 

Get started with Apollo Server

This tutorial helps you: Obtain a basic understanding of GraphQL principlesDefine a GraphQL schema that represents the structure of your data setRun an instance of Apollo Server that lets you execute queries against your schema This tutorial assumes that y

www.apollographql.com

 

반응형