Введение в GraphQL API с JavaScript
GraphQL — это язык запросов для API, разработанный Facebook. В отличие от REST, где каждый эндпоинт возвращает фиксированную структуру данных, GraphQL позволяет клиенту запрашивать только те поля, которые ему нужны. Это уменьшает объем передаваемых данных и упрощает разработку сложных приложений.
В этом туториале мы создадим полноценный GraphQL API на Node.js с использованием Apollo Server. Вы научитесь определять схемы, писать резолверы, выполнять запросы и мутации. Все примеры кода готовы к запуску и проверены на практике.
Для работы вам потребуется Node.js версии 14 или выше и базовое знание JavaScript. Если вы знакомы с Express.js, Apollo Server покажется вам очень похожим — он также использует middleware и работает с HTTP.
Установка и настройка проекта
Создайте новую директорию и инициализируйте проект:
mkdir graphql-tutorial
cd graphql-tutorial
npm init -y
Установите необходимые зависимости:
npm install @apollo/server graphql
npm install nodemon --save-dev
Создайте файл index.js — это будет точка входа в наше приложение.
Базовая структура проекта будет выглядеть так:
graphql-tutorial/
├── node_modules/
├── index.js
├── package.json
└── package-lock.json
Создание первого GraphQL сервера
Начнем с минимального сервера, который возвращает простое сообщение. Откройте index.js и добавьте следующий код:
import { ApolloServer } from '@apollo/server';
import { startStandaloneServer } from '@apollo/server/standalone';
// Определяем схему GraphQL
const typeDefs = `#graphql
type Query {
hello: String
}
`;
// Резолвер для запроса hello
const resolvers = {
Query: {
hello: () => 'Привет, мир! Это первый GraphQL запрос!',
},
};
// Создаем экземпляр сервера
const server = new ApolloServer({
typeDefs,
resolvers,
});
// Запускаем сервер
const { url } = await startStandaloneServer(server, {
listen: { port: 4000 },
});
console.log(`🚀 Сервер запущен на: ${url}`);
Запустите сервер командой node index.js. Откройте браузер и перейдите по адресу http://localhost:4000. Вы увидите интерфейс Apollo Sandbox, где сможете выполнять запросы.
Введите в левой панели:
query {
hello
}
И нажмите кнопку "Run". В ответ вы получите:
{
"data": {
"hello": "Привет, мир! Это первый GraphQL запрос!"
}
}
Работа с типами данных и сложными запросами
GraphQL позволяет определять собственные типы данных. Создадим модель пользователя и книг для нашего API.
Обновите файл index.js:
import { ApolloServer } from '@apollo/server';
import { startStandaloneServer } from '@apollo/server/standalone';
// Определяем типы
const typeDefs = `#graphql
type Book {
id: ID!
title: String!
author: String!
publishedYear: Int
}
type User {
id: ID!
name: String!
email: String!
books: [Book]
}
type Query {
books: [Book]
book(id: ID!): Book
users: [User]
user(id: ID!): User
}
`;
// Тестовые данные
const books = [
{ id: '1', title: 'Гарри Поттер', author: 'Дж.К. Роулинг', publishedYear: 1997 },
{ id: '2', title: 'Властелин колец', author: 'Дж.Р.Р. Толкин', publishedYear: 1954 },
{ id: '3', title: '1984', author: 'Джордж Оруэлл', publishedYear: 1949 },
];
const users = [
{ id: '1', name: 'Иван Иванов', email: 'ivan@example.com', bookIds: ['1', '2'] },
{ id: '2', name: 'Анна Петрова', email: 'anna@example.com', bookIds: ['3'] },
];
// Резолверы
const resolvers = {
Query: {
books: () => books,
book: (_, { id }) => books.find(book => book.id === id),
users: () => users,
user: (_, { id }) => users.find(user => user.id === id),
},
User: {
books: (parent) => {
return books.filter(book => parent.bookIds.includes(book.id));
},
},
};
const server = new ApolloServer({
typeDefs,
resolvers,
});
const { url } = await startStandaloneServer(server, {
listen: { port: 4000 },
});
console.log(`🚀 Сервер запущен на: ${url}`);
Теперь вы можете выполнять более сложные запросы. Например, получить всех пользователей с их книгами:
query {
users {
id
name
email
books {
title
author
}
}
}
Мутации: изменение данных через GraphQL
Мутации в GraphQL используются для создания, обновления и удаления данных. Добавим возможность добавлять новые книги.
Расширьте схему и резолверы:
// Добавляем в typeDefs
type Mutation {
addBook(title: String!, author: String!, publishedYear: Int): Book
deleteBook(id: ID!): Book
}
// Добавляем в resolvers
Mutation: {
addBook: (_, { title, author, publishedYear }) => {
const newBook = {
id: String(books.length + 1),
title,
author,
publishedYear: publishedYear || null,
};
books.push(newBook);
return newBook;
},
deleteBook: (_, { id }) => {
const index = books.findIndex(book => book.id === id);
if (index === -1) {
throw new Error('Книга не найдена');
}
const deletedBook = books.splice(index, 1)[0];
return deletedBook;
},
},
Пример мутации для добавления книги:
mutation {
addBook(title: "Мастер и Маргарита", author: "Михаил Булгаков", publishedYear: 1967) {
id
title
author
}
}
После выполнения мутации вы можете запросить список книг и увидеть новую запись.
Фильтрация и пагинация данных
Для реальных приложений важно уметь фильтровать и пагинировать данные. Добавим поддержку этих возможностей.
Обновите схему:
type Query {
books(filter: BookFilter, pagination: PaginationInput): [Book]
# ... оста