conf-talks

GraphQL сервер

graphql - core package

GraphQL спецификация может быть реализована на любом языке программирования. Для NodeJS такой реализацией является пакет graphql. Это самый главный пакет и с его помощью:

Этот пакет ничего не знает о сети, правах доступа, базах данных. Всё это дело реализуется на другом уровне абстракции.

Вот так схема создается:

import { GraphQLSchema, GraphQLObjectType, GraphQLString } from 'graphql';

const schema = new GraphQLSchema({
  query: new GraphQLObjectType({
    name: 'Query',
    fields: {
      hello: {
        type: GraphQLString,
        resolve: () => 'world',
      }
    }
  })
});

А вот так выполняется запрос:

import { graphql } from 'graphql';

const query = '{ hello }';

// Быстрый легкий вариант
// const result = await graphql(schema, query);

// Вариант с наворотами
const result = await graphql({
  schema,
  source: query, // текст запроса
  contextValue: prepareSomehowContext(), // глобальный контекст для resolve-методов
  variableValues: {}, // переменные для GraphQL-запроса
});

expect(result).toEqual({ data: { hello: "world" } });

Зная как строится схема и выполняются запросы, уже можно писать тесты. А вот чтобы поднять веб-сервер с GraphQL, нужно переходить на следующий уровень абстракции.

Runtime сервер

Каковы обычно требования к серверу? По некому протоколу обслуживать множество запросов от разных клиентов. Это может быть http(s) или websockets, либо вообще что-то экзотическое типа ssh, telnet. Ведь использование GraphQL не ограничено каким-либо сетевым протоколом.

Cейчас давайте остановимся на http протоколе и посмотрим на такой GraphQL-сервер. В работе он должен будет произвести следующие операции:

Некислый набор операций, и на каждом пункте могут быть свои нюансы в реализации. Поэтому в NPM’ах можно найти много разных пакетов, предлагающих разные плюшки. Давайте остановимся на самых основных пакетах:

express-graphql

express-graphql - самый первый в истории пакет в NPM’ах, на котором поднимается GraphQL-сервер. Это даже не сервер а middleware для сервера Express, Connect или Restify.

Если у вас уже есть Express приложение, то добавить uri по которому будет отвечать GraphQL не составит проблем:

import express from 'express';
import graphqlHTTP from 'express-graphql';
import schema from './schema';

const app = express();

app.use('/graphql', graphqlHTTP(req => async ({
  schema,
  graphiql: true,
  context: await prepareSomehowContextDataFromRequest(req),
})));

app.listen(3000);

У этого пакета есть одна проблема - захардкоженное ограничение размера запроса в 100kb. Обычно это не проблема, и элементарная защита от DDOS. Но блин, его нельзя переопределить в настройках.

koa-graphql

koa-graphql - такое же middleware как express-graphql, только для Koa.

import Koa from 'koa';
import mount from 'koa-mount';
import graphqlHTTP from 'koa-graphql';
import schema from './schema';

const app = new Koa();

app.use(mount('/graphql', graphqlHTTP(req => async ({
  schema,
  graphiql: true,
  context: await prepareSomehowContextDataFromRequest(req),
}))));

app.listen(4000);

apollo-server 1.0

apollo-server@1.x.x - полная замена для express-graphql и koa-graphql с набор дополнительных конфигурационных опций. Позволяют делать трейсинг запросов и использовать Apollo Cache Control. Больше гибкости и наворотов по сравнению с express-graphql.

import express from 'express';
import bodyParser from 'body-parser';
import { graphqlExpress, graphiqlExpress } from 'apollo-server-express';
import schema from './schema';

const app = express();
app.use('/graphql', bodyParser.json(), graphqlExpress(req => async ({
  schema,
  context: await prepareSomehowContextDataFromRequest(req),
}));
app.get('/graphiql', graphiqlExpress({ endpointURL: '/graphql' }));

app.listen(5000);

apollo-server 2.0

apollo-server@2.x.x - уже применили совсем другой подход, позаимствованный у graphql-yoga. В этой версии они сделали два ключевых изменения:

Ну и конечно добавили сразу поддержку Subscriptions через PubSub, поддержку загрузки файлов, persisted queries и еще удобнее сделали интеграцию со своими платными сервисами.

Сделали возможность передачи “расчлененной схемы” через typeDefs и resolvers:

import { ApolloServer, gql } from 'apollo-server';

const typeDefs = gql`
  type Query {
    hello: String
  }
`;

const resolvers = {
  Query: {
    hello: () => 'world',
  },
};

const server = new ApolloServer({
  typeDefs,
  resolvers,
  context: ({ req }) => prepareSomehowContextDataFromRequest(req),
  playground: true,
});
server.listen({
  port: 6000,
  endpoint: '/graphql',
  playground: '/playground',
});

Нормальную схему они тоже поддерживают - вместо “расчлененки” typeDefs и resolvers, нужно передать параметр schema как в примерах выше.

graphql-yoga

graphql-yoga - это можно сказать прототип для apollo-server 2.0, авторы которой черпали вдохновление именно с этого пакета. Поэтому базовый функционал у них почти одинаковый.

Есть поддержка Subscriptions через PubSub, загрузки файлов.

import { GraphQLServer } from 'graphql-yoga'

const typeDefs = `
  type Query {
    hello: String
  }
`

const resolvers = {
  Query: {
    hello: () => 'world',
  },
};

const server = new GraphQLServer({
  typeDefs,
  resolvers,
  context: (req) => prepareSomehowContextDataFromRequest(req),
});
server.start({
  port: 7000,
  endpoint: '/graphql',
  playground: '/playground',
})

Вместо typeDefs и resolvers можно передать схему целиком через параметр schema.