Введение: GraphQL и C++ — мощный дуэт для современного API
GraphQL становится стандартом де-факто для построения гибких и эффективных API. В то время как большинство туториалов фокусируются на JavaScript или Python, C++ остаётся незаменимым для высоконагруженных систем, игровых серверов и встраиваемых решений. В этом пошаговом руководстве мы создадим полноценный GraphQL API на C++ с нуля, используя библиотеку graphql-cpp и сервер cpp-httplib.
Вы узнаете, как настроить окружение, определить схему, реализовать резолверы и запустить сервер. Все примеры кода можно сразу применить в реальных проектах.
1. Подготовка окружения и установка зависимостей
Для работы нам понадобятся:
- Компилятор с поддержкой C++17 (GCC 8+, Clang 7+, MSVC 2019+)
- CMake (версия 3.14+)
- Библиотека
graphql-cpp(реализация GraphQL) - Библиотека
cpp-httplib(HTTP-сервер) - Менеджер пакетов vcpkg (опционально, но удобно)
Установка через vcpkg
git clone https://github.com/Microsoft/vcpkg.git
cd vcpkg
./bootstrap-vcpkg.sh
./vcpkg install graphql-cpp cpp-httplib nlohmann-json
Структура проекта
graphql-cpp-api/
├── CMakeLists.txt
├── src/
│ ├── main.cpp
│ ├── schema.cpp
│ ├── schema.h
│ ├── resolvers.cpp
│ └── resolvers.h
└── vcpkg.json
CMakeLists.txt
cmake_minimum_required(VERSION 3.14)
project(GraphQLCppApi)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package(graphql-cpp CONFIG REQUIRED)
find_package(cpp-httplib CONFIG REQUIRED)
find_package(nlohmann_json CONFIG REQUIRED)
add_executable(graphql_server
src/main.cpp
src/schema.cpp
src/resolvers.cpp
)
target_link_libraries(graphql_server
graphql-cpp::graphql-cpp
cpp-httplib::cpp-httplib
nlohmann_json::nlohmann_json
)
Теперь всё готово к написанию кода.
2. Определение GraphQL схемы и типов
Начнём с создания схемы для простого API управления книгами. У нас будут типы Book и Author, а также запросы и мутации.
schema.h
#ifndef SCHEMA_H
#define SCHEMA_H
#include <graphqlcpp/GraphQLSchema.h>
std::shared_ptr<graphql::GraphQLSchema> createSchema();
#endif
schema.cpp
#include "schema.h"
#include "resolvers.h"
std::shared_ptr<graphql::GraphQLSchema> createSchema() {
using namespace graphql;
// Тип Author
auto authorType = object_type("Author", {
field("id", int_type()),
field("name", string_type()),
field("books", list_type(int_type()))
});
// Тип Book
auto bookType = object_type("Book", {
field("id", int_type()),
field("title", string_type()),
field("authorId", int_type()),
field("year", int_type()),
field("genre", string_type())
});
// Query root
auto queryType = object_type("Query", {
field("books", list_type(bookType), {
argument("genre", string_type(), true) // необязательный аргумент
}, resolveBooks),
field("book", bookType, {
argument("id", int_type(), false) // обязательный аргумент
}, resolveBookById),
field("authors", list_type(authorType), resolveAuthors)
});
// Mutation root
auto mutationType = object_type("Mutation", {
field("addBook", bookType, {
argument("title", string_type(), false),
argument("authorId", int_type(), false),
argument("year", int_type(), false),
argument("genre", string_type(), false)
}, resolveAddBook),
field("deleteBook", bool_type(), {
argument("id", int_type(), false)
}, resolveDeleteBook)
});
return std::make_shared<GraphQLSchema>(queryType, mutationType);
}
Обратите внимание на необязательные аргументы — они помечены true в конструкторе argument. Это позволяет клиентам запрашивать книги без фильтра по жанру.
3. Реализация резолверов с реальными данными
Теперь напишем резолверы — функции, которые возвращают данные для каждого поля схемы. Для простоты используем статический вектор в памяти, но в реальном проекте здесь будет подключение к БД.
resolvers.h
#ifndef RESOLVERS_H
#define RESOLVERS_H
#include <graphqlcpp/ResolveInfo.h>
#include <nlohmann/json.hpp>
nlohmann::json resolveBooks(const graphql::ResolveInfo& info);
nlohmann::json resolveBookById(const graphql::ResolveInfo& info);
nlohmann::json resolveAuthors(const graphql::ResolveInfo& info);
nlohmann::json resolveAddBook(const graphql::ResolveInfo& info);
nlohmann::json resolveDeleteBook(const graphql::ResolveInfo& info);
#endif
resolvers.cpp
#include "resolvers.h"
#include <vector>
#include <algorithm>
#include <stdexcept>
namespace {
struct Book {
int id;
std::string title;
int authorId;
int year;
std::string genre;
};
struct Author {
int id;
std::string name;
};
// Начальные данные
std::vector<Book> books = {
{1, "Война и мир", 1, 1869, "Роман"},
{2, "Преступление и наказание", 2, 1866, "Роман"},
{3, "Мастер и Маргарита", 3, 1967, "Фантастика"},
{4, "Евгений Онегин", 4, 1833, "Поэзия"}
};
std::vector<Author> authors = {
{1, "Лев Толстой"},
{2, "Фёдор Достоевский"},
{3, "Михаил Булгаков"},
{4, "Александр Пушкин"}
};
int nextBookId = 5;
} // анонимное пространство имён
nlohmann::json resolveBooks(const graphql::ResolveInfo& info)