Write queries. Get an application. Add business logic.
I have been thinking about a small but annoying backend problem for a long time:
we often already know the data operations our application needs, but we still spend a lot of time manually building the same layers around them.
For every new domain model, the ritual is familiar:
- write repository methods;
- add a service layer;
- create HTTP handlers;
- define request and response shapes;
- update OpenAPI;
- add curl examples;
- write tests;
- wire logging, Docker, health checks, auth, and middleware.
That work is not useless, but a lot of it is repetitive. Worse, the same operation gets described in several places, and those places slowly drift apart.
So I wanted to try a different starting point.
Not spec-first.
Not code-first.
Query-first.
What I mean by query-first
The idea is simple:
if the database operation already describes what the application can do, use that operation as the beginning of the REST application.
For SQL projects, that means:
PostgreSQL schema + SQLC queries
↓
Generated endpoint inventory
↓
Repository → Service → HTTP handlers
↓
OpenAPI + auth policies + tests + runtime scaffolding
For MongoDB projects, the same idea starts from explicit YAML contracts:
MongoDB collection contracts
↓
Generated endpoint inventory
↓
Repository → Service → HTTP handlers
↓
OpenAPI + auth policies + tests + runtime scaffolding
The important part is the middle step: an endpoint inventory.
It is a normalized internal model that knows:
- HTTP method and path;
- source query or Mongo method;
- path/query/body parameters;
- result shape;
- auth policy;
- roles;
- system routes such as health, readiness, Swagger, and metrics.
Once that model exists, the generator can render several parts of the application from the same source of truth.
That is the real trick. The generated handler, OpenAPI operation, curl example, test case, and auth policy should not all guess what the endpoint is. They should all come from the same inventory.
The project I built
I implemented this idea in an open-source Go CLI called rest:
Repository: github.com/repomz/rest
It is inspired by sqlc. SQLC turns SQL into type-safe Go code. rest takes the next step and turns SQLC output or MongoDB contracts into a runnable layered REST application.
The goal is not to generate your final product.
The goal is to remove the boring, error-prone scaffolding so the developer can start from a strong application skeleton and then add the actual business logic.
In many simple projects, that may get you very close to a working application. In more complex projects, it should still save the first 80–90% of repetitive setup.
A SQL example
Imagine a table:
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
);
And a named 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 gives typed Go code for the query. rest can then infer an endpoint such as:
GET /studies?type=CT&surgeon=Ivanov
Optional sqlc.narg(...) values become optional query parameters. The result type becomes part of the response model and OpenAPI schema.
There is a small convention layer:
| Query prefix | HTTP method |
|---|---|
Get, List, Find, Search
|
GET |
Delete, Remove, SoftDelete
|
DELETE |
Update, Patch
|
PATCH |
| everything else | POST |
Conventions are never perfect. But they are useful when the alternative is repeatedly describing the same operation in SQL, Go handlers, DTOs, tests, and OpenAPI by hand.
MongoDB contracts
For MongoDB, there is no SQLC equivalent, so I used explicit YAML contracts.
The flow looks like this:
rest init --example mongo
rest gen
go test ./...
Mongo contracts live in:
rest_config/rest_mongo/*.yaml
Each entity file can describe:
- collection name;
- fields;
- indexes;
- CRUD methods;
- custom methods such as
find_one,find_many,update_one,delete_one, andaggregate; - HTTP path/method mapping.
The generated Mongo app includes repositories, services, handlers, OpenAPI, handler tests, auth middleware, Docker support, and runtime health/readiness paths.
The Mongo domain layer is intentionally generic enough for document-shaped data. I did not want to pretend every Mongo collection should behave like a rigid relational table.
What gets generated
Depending on configuration, rest gen can generate:
-
cmd/main.go; - domain models;
- PostgreSQL or MongoDB repositories;
- service layer;
- HTTP handlers;
- request/response models;
- handler tests;
- OpenAPI and Swagger UI;
- JWT or 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;
- CI workflow templates;
- curl examples.
The generated Go code is formatted with goimports, so imports and formatting are normalized immediately.
Auth generation
Auth was one of the harder parts to design because I did not want it to be hidden magic.
The flow is:
- Set
auth: enableinrest.yaml. - Run
rest gen. - The generator discovers endpoints and creates
rest_config/auth_rest.yaml. - The developer marks which endpoints are public, protected, and role-restricted.
- Run
rest genagain. - The generator updates middleware, route wrapping, auth handlers, and OpenAPI security.
JWT and Basic Auth are both supported.
For SQL/JWT apps, the generator can create signup/signin handlers, password hashing, token service, claims configuration, and protected routes based on the configured user model.
For Basic Auth and Mongo apps, it generates the matching middleware and role checks.
rest doctor
I also added a diagnostic command:
rest doctor
It can be run at any stage:
- after
rest init; - after editing YAML files;
- after
rest gen; - before trying to run the generated app.
It checks config consistency, missing tools, YAML mistakes, Docker readiness, OpenAPI/auth configuration, generated files, and runtime prerequisites.
This is something I personally wanted when learning programming years ago: a command that tells me what is missing and what to do next.
What it does not try to solve
rest does not know your business.
It will not generate:
- complex domain validation;
- custom workflows;
- billing logic;
- external integrations;
- complicated transaction boundaries;
- product-specific authorization rules.
Those belong in application code.
The generator is meant to create a strong, coherent starting point. After that, the developer can continue normally — and may only return to the generator when new domain models or queries need another layer of scaffolding.
Try it
Install:
go install github.com/repomz/rest/cmd/rest@latest
SQL example:
rest init --example sql
rest gen
go test ./...
MongoDB example:
rest init --example mongo
rest gen
go test ./...
Then inspect:
rest list endpoints
rest doctor
Repository:
Why I am sharing it
I am not claiming query-first is the only right way to build APIs.
Spec-first is still great when OpenAPI is the central contract. Code-first is still convenient for many teams. Manual code is still the right answer when the business logic is the hard part.
But I think there is a useful space between SQLC and a full application framework:
if the query already describes the operation, let it generate the boring layers around that operation.
That is the space I wanted to explore with rest.
If the idea sounds interesting, try one of the examples and bring an inconvenient real-world query or Mongo contract to the issue tracker. Inconvenient examples are how generators become honest.
Write queries. Get an application. Add business logic.

Top comments (0)