Я давно хотел убрать из backend‑разработки одну особенно липкую рутину: когда для каждой новой сущности снова и снова приходится собирать одни и те же слои REST API — repository, service, handlers, request/response модели, OpenAPI, auth, тесты, curl‑примеры, Docker и прочую инфраструктуру.В этой статье рассказываю про query‑first подход и open‑source Go CLI rest, который позволяет реализовать эту идею. Смысл простой: если SQL‑запросы или MongoDB‑контракты уже описывают, какие операции нужны приложению, то из них можно сгенерировать согласованный каркас REST‑сервиса.В статье показано, как это работает для PostgreSQL и MongoDB, что именно генерируется, что такоеrest doctor и почему цель инструмента — не заменить бизнес‑логику, а снять первые 80–90% повторяющейся ручной работы. Читать далее

Я уже давно думал об одной вроде небольшой, но на самом деле изрядно, по крайней мере меня, доставшей backend‑проблеме.
В зависимости от подхода, конечно, но зачастую, мы уже знаем, какие операции и с какими сущностями нам предстоит работать, создавая REST сервис. Но написание моделей и запросов к базе данных всегда проходит лишь как один из этапов написания слоистой архитектуры будущего приложения/микросервиса. Несмотря на опыт, всё же приходится многократно проходить ручную сборку одних и тех же слоёв по каждому домену, не говоря уже о нейминге эндпоинтов, хендлеров, хелперов и так далее...
Почти для каждой новой доменной модели мы проходим один и тот же путь:
— написать методы репозитория;
— добавить сервисный слой;
— создать HTTP handlers;
— описать request и response модели;
— реализовать auth и авторизацию с ролями для эндпоинтов;
— написать/обновить OpenAPI spec;
— добавить curl‑примеры; // по желанию, но мне часто нравилось работать с ними
— написать тесты;
— подключить логирование, Docker, health checks, auth и кучку middleware.
Большая часть работы повторяется из проекта в проект. Беря за основу любимый шаблон все равно мы долго топчемся над реализацией однотипных сущностей прыгая по этому snakeCase, фантазируя над наименованием очередного сервиса, интерфейса, хендлера, хелпера... кажется я уже повторяюсь:).
Подходов к началу создания REST сервиса, как мы знаем, немного:
— spec‑first
— code‑first
— whateverIusedToDo‑first — как привык, так пишу + зависит от настроения и погоды // самый частый не признанный, но всеми любимый
— query‑first — мне всегда импонировал, но дать название этому подходу (по крайней мере в литературе не встречал сам — ни на что не претендую и готов признать, коли укажете, что такое есть) вдруг захотелось в этом году...а потом долго писал первые пару строк генератора, да-да, люблю писать сам...но только недолго:).
Что я называю query‑first
Идея простая: если есть модель и операция с базой данных уже описывает, что конкретно приложение должно делать с тем или иным json, можно использовать эту операцию для создания абсолютно (почти) всего REST приложения... Не претендую быть первым кто это придумал, а может и реализовал, ну разве что 1001ым:).
Для SQL проектов это выглядит так:
PostgreSQL schema + SQLC queries
↓
Generated endpoint inventory
↓
Repository → Service → HTTP handlers
↓
http server со всеми "примочками" +
logging + OpenAPI + auth policies +
tests + observability + docker +
docker compose + ci/cdДля MongoDB проектов идея похожая, только вместо SQLC используются явные YAML‑контракты:
MongoDB collection contracts
↓
Generated endpoint inventory
↓
Repository → Service → HTTP handlers
↓
http server со всеми "примочками" +
logging + OpenAPI + auth policies +
tests + observability + docker +
docker compose + ci/cdendpoint inventory — это внутренняя модель, которая знает:
— HTTP method и path;
— исходный SQL query или Mongo method;
— path/query/body параметры;
— auth policy;
— роли;
— системные routes вроде health, readiness, Swagger и metrics.
Генератор может создавать разные части приложения из одного источника правды. Handler, OpenAPI operation, curl‑пример, тест и auth policy не должны каждый отдельно угадывать, что такое endpoint. Они должны строиться из одного inventory.
В общем, реализовал я эту идею в open‑source Go CLI под названием rest. Не могу не сказать, что вдохновил меня изначально давний зарекомендовавший себя проект sqlc.
Именно этот проект вдохновил меня сначала начать, затем продолжить, начатое давно и наконец завершить первый релиз. SQLC превращает SQL в type‑safe Go код. rest делает следующий шаг: берёт SQLC output или MongoDB contracts и превращает их в запускаемое слоистое REST приложение.
Цель не в том, чтобы сгенерировать финальный продукт. Цель простая — автоматизировать — убрать скучную и склонную к ошибкам рутину, чтобы разработчик стартовал не с пустой папки, а с хорошего каркаса приложения и дальше писал настоящую бизнес‑логику. В простых проектах это может дать почти готовое приложение. В более сложных — всё равно снять первые 80–90% повторяющейся работы.
SQL примерПредставим таблицу:
CREATE TABLE studies (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
patient TEXT NOT NULL,
study_type TEXT NOT NULL,
time_beginning TIMESTAMP,
surgeon TEXT NOT NULL,
deleted BOOLEAN NOT NULL DEFAULT FALSE
);И sqlc query:
-- name: GetStudies :many
SELECT * FROM studies
WHERE deleted = false
AND (
sqlc.narg('type')::text IS NULL
OR study_type = sqlc.narg('type')
)
AND (
sqlc.narg('surgeon')::text IS NULL
OR surgeon = sqlc.narg('surgeon')
)
ORDER BY time_beginning DESC;Далее должно идти выполнение команды sqlc generate самостоятельно или автоматическое в рамках самой команды rest gen при активной опции auto_sqlc: enable в файле настроек rest.yaml
SQLC создаёт type‑safe Go код для этого запроса. rest может на его основе вывести endpoint вроде:
GET /studies?type=CT&surgeon=IvanovОпциональные sqlc.narg(...) значения становятся optional query parameters. Result type становится частью response model и OpenAPI schema.
Для MongoDB нет прямого аналога SQLC, поэтому я использовал явные YAML‑контракты. Mongo contracts лежат здесь: rest_config/rest_mongo/*.yaml.
Каждый entity file может описывать:
— collection name;
— fields;
— indexes;
— CRUD methods;
— custom methods вроде find_one, find_many, update_one, delete_one и aggregate;
— HTTP path/method mapping.
Сгенерированное Mongo app включает repositories, services, handlers, OpenAPI, handler tests, auth middleware, Docker support и runtime health/readiness paths. Mongo domain layer сделан достаточно гибким для document data.
Что генерируетсяВ зависимости от конфигурации rest.yaml команда rest gen может сгенерировать:
— cmd/main.go;
— domain models;
— PostgreSQL или MongoDB repositories;
— service layer;
— HTTP handlers;
— request/response models;
— handler tests;
— OpenAPI и Swagger UI;
— JWT или Basic Auth;
— RBAC route wrapping;
— security headers;
— rate limiting;
— production‑safer CORS defaults;
— request IDs;
— panic recovery;
— body size limits;
— Zap logging;
— optional Prometheus metrics;
— Dockerfile;
— optional Docker Compose;
— .env.example;
— Makefile;
— init_db shell script для локального postgres
— CI/CD workflow templates;
— curl examples.
— DEPLOYMENT.md next steps guide
— ARCHITECTURE.md c текущей схемой архитектуры проекта
— README шаблон с инструкцией по запуску проекта
Сгенерированный Go код форматируется линтерами автоматически, поэтому imports и общий стиль кода сразу приводятся к нормальному виду.
Auth generationAuth оказался одной из самых сложных частей дизайна. Поддерживается пока 2 варианта JWT и BasicAuth.
Flow такой:
1. Указать auth: enable в rest.yaml.
2. Запустить rest gen.
3. Генератор найдёт endpoints и создаст rest_config/auth_rest.yaml.
4. Разработчик выбирает вариант auth и отмечает, какие endpoints публичные, какие защищены, а какие требуют роли.
5. Снова запустить rest gen.
6. Генератор обновит middleware, route wrapping, auth handlers и OpenAPI security.
Для SQL/JWT приложений генератор может создать signup/signin handlers, password hashing, token service, claims configuration и protected routes на основе настроенной user model.
Для Basic Auth и Mongo apps генерируется соответствующий middleware и role checks.
rest doctorДа, и такая есть фича. Её можно запускать на любом этапе:
— после rest init;
— после редактирования YAML файлов;
— после rest gen;
— перед попыткой запустить сгенерированное приложение.
Она проверяет согласованность конфигов, наличие нужных tools, YAML‑ошибки, готовность Docker, OpenAPI/auth configuration, generated files и наличие необходимых библиотек.
Это как раз та вещь, которой мне не хватало много лет назад, в самом начале пути: команда, которая говорит, чего не хватает и что делать дальше... в точности, как сейчас любой ai powered cli.
Что проект не пытается решитьrest не знает вашу бизнес‑логику.
Он не будет генерировать:
— сложную domain validation;
— custom workflows;
— billing logic;
— external integrations;
— сложные transactions;
— product‑specific authorization rules.
— неудобный реальный SQL query с которым не справится sqlc generate или «крайне сложный» Mongo contract
Генератор нужен, чтобы создать сильную и согласованную стартовую точку. После этого разработчик продолжает обычную разработку. К генератору может возвращаться только тогда, когда появляются новые domain models или queries, для которых снова нужно собрать все слои и добавить к существующим — об этом позаботится скромная опция safe_reload: enable из rest.yaml, которая спросит вас, сохранить ли ваш кастомный код при повторной генерации.
Как попробоватьInstall:
go install github.com/repomz/rest/cmd/rest@latestSQL пример:
rest init --example sql # создает рабочий пример модели и операций для sqlc
rest gen
go run ./cmd/main.go # go test ./... MongoDB пример:
rest init --example sql # создает рабочий пример модели и операций для sqlc
rest gen
go run ./cmd/main.go # go test ./... Реальный workflow:
rest init
# Создаёт rest_config/ с базовыми настройками генератора.
# Перед генерацией нужно заполнить rest.yaml и добавить контракты:
# SQLC schema/queries для PostgreSQL или YAML-контракты для MongoDB.
rest gen
go run ./cmd/main.go # go test ./... После генерации проекта можно взглянуть на схему ендпоинтов:
rest list endpoints # всегда покажет все актуальные ендпоинты с их текущими опциямиили просто заглянуть в swagger ui: http://localhost:8080/swagger
В README.md вас будет ждать пара шильдиков и текущая архитектура вашего приложения:
my-app/
├── cmd/
│ └── main.go # entrypoint приложения
├── internal/
│ ├── config/ # загрузка env/config
│ ├── db/ # SQLC models/queries или Mongo client
│ ├── repository/ # доступ к данным
│ ├── service/ # бизнес-слой
│ ├── transport/
│ │ └── http/ # handlers, routes, middleware
│ ├── auth/ # JWT/Basic Auth, RBAC
│ ├── logger/ # Zap logging
│ └── observability/ # health, readiness, metrics
├── api/
│ └── openapi.yaml # Swagger/OpenAPI схема
├── examples/
│ └── curl/ # curl-примеры запросов
├── rest_config/
│ ├── rest.yaml # основные настройки генерации
│ ├── auth_rest.yaml # политики доступа к endpoints
│ ├── rest_sqlc/ # SQLC schema/queries/config
│ │ ├── rest_sqlc.yaml
│ │ ├── schema.sql
│ │ └── queries.sql
│ └── rest_mongo/ # Mongo contracts, если используется MongoDB
│ ├── rest_cheatsheet.yaml
│ └── user.yaml
├── Dockerfile
├── docker-compose.yaml
├── Makefile
├── .env.example
├── go.mod
└── go.sumПочему и зачемЯ не утверждаю, что query‑first — единственный правильный способ строить API, но я думаю это просто УДОБНО. Spec‑first отлично подходит, когда OpenAPI является центральным контрактом. Свой Code‑first удобен для многих команд и у каждого beckend'ера конечно возможно есть свой личный кастомный шаблон/генератор. Ручной код остаётся правильным ответом, когда главная сложность — в бизнес‑логике.
Но свой whateverIusedToDo‑first я точно апгрейдил и с удовольствие делюсь с вами, не предтендуя ни на что ни в коем разе, а наоборот, имея большое желание получить нескромный совет по доработке, улучшению и определенно по борьбе с bug issues. Ну, а кому проект возможно сэкономит хоть сколько то времени в его работе и станет, возможно, очередным полезным инструментом, могу с улыбкой только пожелать — Give yourself a little rest.
Если идея кажется интересной, WELCOME TO COLLABORATE and rest together.
Write queries. Get an application. Add business logic.
| # | Наименование новости | Тональность | Информативность | Дата публикации |
|---|---|---|---|---|
| 1 | Как желание написать простой CRUD привело к созданию целой видеоплатформы | 5 | 7 | 22-06-2026 |
| 2 | Оптимизация без AI: как я автоматизировал API-ручки и типы | -2 | 5 | 29-06-2026 |
| 3 | AngaraBase: новая HTAP СУБД | 7 | 8 | 29-06-2026 |
| 4 | ORM — есть ли профит? Особенно когда по двору бегает ИИ-шка | 3 | 6 | 23-06-2026 |
| 5 | Кто выгрузил платежи, или Пример расследования инцидента на аудите в Postgres Pro Enterprise | 0 | 7 | 10-07-2026 |
| 6 | MinIO, MongoDB, PostgreSQL для хранения 25 лет истории стоимости акций | 0 | 7 | 14-07-2026 |
| 7 | Сможет ли DB Client от OpenIDE заменить DataGrip? | 0 | 5 | 16-07-2026 |
| 8 | HTTP получил метод QUERY: зачем понадобился безопасный запрос с телом | 0 | 6 | 03-07-2026 |
| 9 | Многоэтапные сборки в Docker: как уменьшить образ с 1,2 ГБ до 50 МБ | 5 | 8 | 28-06-2026 |
| 10 | [Перевод] Ограничения целостности с отложенной проверкой в PostgreSQL | 0 | 7 | 06-07-2026 |