GraphQL API с C++: пошаговый туториал
GraphQL — это мощный язык запросов для API, который позволяет клиентам получать именно те данные, которые им нужны, и ничего лишнего. Хотя C++ не является самым популярным языком для создания веб-API, его производительность и контроль над памятью делают его отличным выбором для высоконагруженных систем. В этом туториале мы создадим работающий GraphQL сервер на C++ с использованием библиотеки graphql-cpp.
Что потребуется для старта
Для работы вам понадобятся:
- Компилятор с поддержкой C++17 (GCC 8+, Clang 7+, MSVC 2019+)
- CMake версии 3.14 или выше
- Библиотека
graphql-cpp(установка через vcpkg или сборка из исходников) - Библиотека
libmicrohttpdдля HTTP-сервера
Установим зависимости через vcpkg:
git clone https://github.com/microsoft/vcpkg.git
cd vcpkg
./bootstrap-vcpkg.sh
./vcpkg install graphql-cpp libmicrohttpd
Создание базовой структуры проекта
Начнём с настройки CMake проекта. Создайте файл CMakeLists.txt:
cmake_minimum_required(VERSION 3.14)
project(graphql-cpp-tutorial)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package(graphql-cpp CONFIG REQUIRED)
find_package(MHD CONFIG REQUIRED)
add_executable(graphql-server main.cpp)
target_link_libraries(graphql-server
graphql-cpp::graphql-cpp
MHD::MHD
)
Теперь создадим основной файл main.cpp с минимальным сервером:
#include <graphql-cpp/GraphQL.h>
#include <microhttpd.h>
#include <iostream>
#include <string>
using namespace graphql::cpp;
// Функция обработки HTTP запросов
static enum MHD_Result request_handler(void* cls,
struct MHD_Connection* connection,
const char* url,
const char* method,
const char* version,
const char* upload_data,
size_t* upload_data_size,
void** con_cls) {
const char* response_str = "{\"data\":{\"hello\":\"world\"}}";
struct MHD_Response* response = MHD_create_response_from_buffer(
strlen(response_str),
(void*)response_str,
MHD_RESPMEM_PERSISTENT
);
MHD_add_response_header(response, "Content-Type", "application/json");
int ret = MHD_queue_response(connection, MHD_HTTP_OK, response);
MHD_destroy_response(response);
return ret == MHD_YES ? MHD_YES : MHD_NO;
}
int main() {
struct MHD_Daemon* daemon = MHD_start_daemon(
MHD_USE_INTERNAL_POLLING_THREAD,
4000,
NULL, NULL,
&request_handler,
NULL,
MHD_OPTION_END
);
if (daemon == NULL) {
std::cerr << "Failed to start server" << std::endl;
return 1;
}
std::cout << "Server running on http://localhost:4000/graphql" << std::endl;
getchar();
MHD_stop_daemon(daemon);
return 0;
}
Определение GraphQL схемы
Теперь добавим настоящую GraphQL схему. Создадим тип Book и корневой тип Query:
#include <graphql-cpp/Schema.h>
#include <graphql-cpp/Type.h>
#include <vector>
#include <memory>
using namespace graphql::cpp;
// Модель данных
struct Book {
int id;
std::string title;
std::string author;
int year;
};
// Репозиторий книг (имитация базы данных)
class BookRepository {
public:
static std::vector<Book> getAll() {
return {
{1, "Война и мир", "Лев Толстой", 1869},
{2, "Преступление и наказание", "Фёдор Достоевский", 1866},
{3, "Мастер и Маргарита", "Михаил Булгаков", 1967}
};
}
static Book getById(int id) {
auto books = getAll();
for (const auto& book : books) {
if (book.id == id) return book;
}
throw std::runtime_error("Book not found");
}
};
// Создание GraphQL схемы
std::shared_ptr<Schema> createSchema() {
auto schema = std::make_shared<Schema>();
// Тип Book
auto bookType = std::make_shared<ObjectType>("Book");
bookType->addField("id", Type::NonNullInt());
bookType->addField("title", Type::NonNullString());
bookType->addField("author", Type::NonNullString());
bookType->addField("year", Type::Int());
// Корневой тип Query
auto queryType = std::make_shared<ObjectType>("Query");
// Поле books: возвращает список всех книг
queryType->addField("books", Type::List(bookType),
[](const ResolverArgs& args) -> json {
auto books = BookRepository::getAll();
json result = json::array();
for (const auto& book : books) {
result.push_back({
{"id", book.id},
{"title", book.title},
{"author", book.author},
{"year", book.year}
});
}
return result;
}
);
// Поле book: возвращает книгу по ID
queryType->addField("book", bookType,
[](const ResolverArgs& args) -> json {
int id = args.getArgument<int>("id");
auto book = BookRepository::getById(id);
return {
{"id", book.id},
{"title", book.title},
{"author", book.author},
{"year", book.year}
};
},
Argument("id", Type::NonNullInt())
);
schema->setQueryType(queryType);
return schema;
}
Добавление мутаций (изменение данных)
Добавим возможность добавлять новые книги через мутации:
// Мутация для добавления книги
queryType->addField("addBook", bookType,
[](const ResolverArgs& args) -> json {
static int nextId = 4;
Book newBook;
newBook.id = nextId++;
newBook.title = args.getArgument<std::string>("title");
newBook.author = args.getArgument<std::string>("author");
newBook.year = args.getArgument<int>("year");
// В реальном приложении здесь было бы сохран