GraphQL.js для JavaScript (Node.js): полное руководство с примерами

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

GraphQL.js для JavaScript (Node.js): Полное руководство

1. Что это и зачем нужна?

GraphQL.js — это эталонная реализация спецификации GraphQL на языке JavaScript. Она предоставляет полный набор инструментов для создания, выполнения и валидации GraphQL-запросов на серверной стороне (Node.js) и в браузере. В отличие от REST API, где каждый эндпоинт возвращает фиксированную структуру данных, GraphQL позволяет клиенту точно запросить только те данные, которые ему нужны, и ничего лишнего. Это решает проблему over-fetching (получение избыточных данных) и under-fetching (недостаток данных), что критически важно для производительности мобильных приложений и сложных веб-интерфейсов.

Библиотека GraphQL.js является фундаментом для всех популярных GraphQL-фреймворков в экосистеме JavaScript, таких как Apollo Server, Express-GraphQL, Yoga и других. Она берет на себя тяжелую работу по парсингу запросов, их валидации, выполнению резолверов и формированию ответа в строгом соответствии со спецификацией. Используя GraphQL.js напрямую, вы получаете максимальный контроль над процессом обработки запросов, что особенно важно при создании кастомных middleware, инструментов для тестирования или интеграции с нестандартными источниками данных.

Основные преимущества использования GraphQL.js включают: строгую типизацию схемы, автоматическую генерацию документации (через Introspection Query), возможность эффективно объединять данные из нескольких источников (базы данных, REST API, микросервисы) в одном запросе, а также встроенные механизмы для обработки ошибок и пагинации. Это делает библиотеку незаменимой для построения современных, масштабируемых и гибких API.

2. Установка

Установка библиотеки производится через менеджер пакетов npm (Node Package Manager). Убедитесь, что у вас установлен Node.js версии 12 или выше.

npm install graphql

Для использования в проекте с поддержкой ES-модулей (type: "module" в package.json) импортируйте функции следующим образом:

import { graphql, buildSchema } from 'graphql';

Для CommonJS (стандартный подход в Node.js):

const { graphql, buildSchema } = require('graphql');

3. Быстрый старт — минимальный рабочий пример

Ниже представлен простейший сервер, который принимает GraphQL-запрос и возвращает приветствие. Этот пример демонстрирует ядро библиотеки: определение схемы, резолверы и выполнение запроса.

const { graphql, buildSchema } = require('graphql');

// 1. Определяем схему (Schema Definition Language - SDL)
const schema = buildSchema(`
  type Query {
    hello: String
  }
`);

// 2. Определяем корневой резолвер (root resolver)
const rootValue = {
  hello: () => {
    return 'Hello, GraphQL.js!';
  },
};

// 3. Выполняем запрос
const query = '{ hello }';

graphql({
  schema,
  source: query,
  rootValue,
}).then((response) => {
  console.log(JSON.stringify(response, null, 2));
  // Вывод: { "data": { "hello": "Hello, GraphQL.js!" } }
});

4. Основные методы/функции/классы

4.1. buildSchema(schemaString: string): GraphQLSchema

Что делает: Парсит строку, написанную на языке схем GraphQL (SDL), и создает объект GraphQLSchema. Это самый быстрый способ определить схему без использования программного конструктора.

const { buildSchema } = require('graphql');

const schema = buildSchema(`
  type Query {
    user(id: ID!): User
  }
  type User {
    id: ID
    name: String
  }
`);

4.2. graphql(args: GraphQLArgs): Promise<GraphQLResult>

Что делает: Основная функция для выполнения GraphQL-запроса. Принимает объект с параметрами (схема, строка запроса, корневое значение, контекст, переменные) и возвращает Promise с результатом выполнения.

const { graphql, buildSchema } = require('graphql');

const schema = buildSchema(`type Query { echo(msg: String!): String }`);
const rootValue = { echo: ({ msg }) => `Echo: ${msg}` };

graphql({ schema, source: '{ echo(msg: "test") }', rootValue }).then(console.log);
// { data: { echo: 'Echo: test' } }

4.3. GraphQLSchema(config: GraphQLSchemaConfig): GraphQLSchema

Что делает: Класс-конструктор для программного создания схемы. Позволяет задавать типы, поля и резолверы через объекты конфигурации, а не через SDL. Дает больше контроля, например, для добавления кастомных скалярных типов.

const { GraphQLSchema, GraphQLObjectType, GraphQLString } = require('graphql');

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

4.4. GraphQLObjectType(config: GraphQLObjectTypeConfig): GraphQLObjectType

Что делает: Определяет объектный тип в схеме. Содержит поля, каждое из которых имеет свой тип и резолвер. Используется для создания сложных типов данных.

const { GraphQLObjectType, GraphQLString, GraphQLInt } = require('graphql');

const UserType = new GraphQLObjectType({
  name: 'User',
  fields: {
    id: { type: GraphQLInt },
    name: { type: GraphQLString },
  },
});

4.5. GraphQLNonNull(type: GraphQLNullableType): GraphQLNonNull

Что делает: Модификатор типа, который указывает, что значение поля не может быть null. Если резолвер вернет null для такого поля, GraphQL вызовет ошибку выполнения.

const { GraphQLNonNull, GraphQLString } = require('graphql');

const nonNullString = GraphQLNonNull(GraphQLString);
// Использование в схеме: id: { type: new GraphQLNonNull(GraphQLInt) }

4.6. GraphQLList(type: GraphQLType): GraphQLList

Что делает: Модификатор типа для обозначения списка (массива) элементов определенного типа. Позволяет возвращать коллекции данных.

const { GraphQLList, GraphQLString } = require('graphql');

const listOfStrings = GraphQLList(GraphQLString);
// Использование: users: { type: new GraphQLList(UserType) }

4.7. GraphQLEnumType(config: GraphQLEnumTypeConfig): GraphQLEnumType

Что делает: Определяет перечисляемый тип — набор фиксированных строковых значений. Полезен для статусов, ролей, категорий.

const { GraphQLEnumType } = require('graphql');

const RoleType = new GraphQLEnumType({
  name: 'Role',
  values: {
    ADMIN: { value: 'admin' },
    USER: { value: 'user' },
    GUEST: { value: 'guest' },
  },
});

4.8. GraphQLInputObjectType(config: GraphQLInputObjectTypeConfig): GraphQLInputObjectType

Что делает: Определяет входной объектный тип, используемый для передачи сложных аргументов в мутации или запросы. В отличие от GraphQLObjectType, не может иметь резолверов.

const { GraphQLInputObjectType, GraphQLString, GraphQLNonNull } = require('graphql');

const UserInputType = new GraphQLInputObjectType({
  name: 'UserInput',
  fields: {
    name: { type: new GraphQLNonNull(GraphQLString) },
    email: { type: GraphQLString },
  },
});

4.9. execute(args: ExecutionArgs): PromiseOrValue<ExecutionResult>

Что делает: Низкоуровневая функция для выполнения запроса. В отличие от graphql, не включает в себя парсинг и валидацию — ожидает, что запрос уже представлен в виде DocumentNode. Используется для продвинутой оптимизации.

const { parse, validate, execute, buildSchema } = require('graphql');

const schema = buildSchema(`type Query { ping: String }`);
const document = parse('{ ping }');
const errors = validate(schema, document);
if (errors.length === 0) {
  execute({ schema, document, rootValue: { ping: () => 'pong' } }).then(console.log);
}

4.10. parse(source: string | Source): DocumentNode

Что делает: Преобразует строку GraphQL-запроса в абстрактное синтаксическое дерево (AST). Это первый этап обработки запроса, который может быть выполнен отдельно для кэширования или анализа.

const { parse } = require('graphql');

const ast = parse('{ __typename }');
console.log(ast.definitions[0].kind); // 'OperationDefinition'

5. Реальный практический пример (100-200 строк кода)

Создадим простой API для управления списком задач (TODO) с поддержкой запросов, мутаций и аргументов. Этот пример показывает, как объединить несколько концепций библиотеки в работающий сервер.

const { graphql, buildSchema } = require('graphql');

// Имитация базы данных
let todos = [
  { id: 1, title: 'Изучить GraphQL.js', completed: false },
  { id: 2, title: 'Написать документацию', completed: true },
];
let nextId = 3;

// Определение схемы
const schema = buildSchema(`
  type Todo {
    id: ID!
    title: String!
    completed: Boolean!
  }

  type Query {
    todos(completed: Boolean): [Todo]
    todo(id: ID!): Todo
  }

  type Mutation {
    addTodo(title: String!): Todo
    toggleTodo(id: ID!): Todo
    deleteTodo(id: ID!): Boolean
  }
`);

// Корневой резолвер
const rootValue = {
  todos: ({ completed }) => {
    if (completed === undefined) return todos;
    return todos.filter(todo => todo.completed === completed);
  },
  todo: ({ id }) => todos.find(todo => todo.id === Number(id)),

  addTodo: ({ title }) => {
    const newTodo = { id: nextId++, title, completed: false };
    todos.push(newTodo);
    return newTodo;
  },
  toggleTodo: ({ id }) => {
    const todo = todos.find(todo => todo.id === Number(id));
    if (!todo) throw new Error(`Todo with id ${id} not found`);
    todo.completed = !todo.completed;
    return todo;
  },
  deleteTodo: ({ id }) => {
    const index = todos.findIndex(todo => todo.id === Number(id));
    if (index === -1) return false;
    todos.splice(index, 1);
    return true;
  },
};

// Выполнение тестовых запросов
async function main() {
  // Запрос всех задач
  const query1 = '{ todos { id title completed } }';
  const res1 = await graphql({ schema, source: query1, rootValue });
  console.log('All todos:', JSON.stringify(res1.data));

  // Добавление новой задачи
  const mutation1 = 'mutation { addTodo(title: "Купить молоко") { id title completed } }';
  const res2 = await graphql({ schema, source: mutation1, rootValue });
  console.log('Added todo:', JSON.stringify(res2.data));

  // Запрос только незавершенных задач
  const query2 = '{ todos(completed: false) { title } }';
  const res3 = await graphql({ schema, source: query2, rootValue });
  console.log('Incomplete todos:', JSON.stringify(res3.data));

  // Удаление задачи
  const mutation2 = 'mutation { deleteTodo(id: 1) }';
  const res4 = await graphql({ schema, source: mutation2, rootValue });
  console.log('Deleted todo with id 1:', res4.data);
}

main().catch(console.error);

6. Советы по производительности и типичные ошибки

Советы по производительности

  • Используйте DataLoader для N+1 проблемы: Когда резолверы для списка объектов делают отдельные запросы к БД для каждого элемента, это приводит к N+1 проблеме. DataLoader (отдельная библиотека) позволяет батчить и кэшировать запросы на уровне одного HTTP-запроса.
  • Кэшируйте парсинг и валидацию: Если вы получаете много одинаковых запросов (например, { __typename }), используйте parse и validate отдельно и кэшируйте результат по строке запроса. Функция graphql делает это каждый раз заново.
  • Ограничивайте глубину и сложность запросов: Злоумышленник может отправить глубоко вложенный запрос (например, { user { friends { user { friends {...} } } } }), который перегрузит сервер. Используйте библиотеки типа graphql-depth-limit или graphql-query-complexity для защиты.
  • Избегайте дорогих вычислений в резолверах: Резолверы должны быть легковесными. Если нужно выполнить тяжелую работу, делайте это асинхронно и возвращайте Promise. Рассмотрите использование "lazy loading" для полей, которые запрашиваются редко.
  • Используйте execute для production: В высоконагруженных системах используйте execute вместо graphql, чтобы контролировать этапы парсинга и валидации, а также кэшировать AST.

Типичные ошибки

  • Забыли вернуть Promise из асинхронного резолвера: Если резолвер помечен как async, он всегда возвращает Promise. Если вы забыли

Рекомендаций