// API-REST-GENERATOR — SCHEMA IN, BACKEND OUT

api-rest-generator v0.2.0 · Rust 2021 + clap + regex + once_cell + java-properties · 4 SQL dialects (MySQL / PostgreSQL / SQLite / MSSQL) · 4 output targets (Java / Kotlin / Groovy Spring Boot + Rust/Loco Axum+SeaORM)

2 binaries (api-rest-generator multi-target + loco-gen Rust-only) · mid-transition: original Kotlin/Gradle pipeline still ships alongside the Rust port (parity-tested)

Report GitHub Issues
// Color scheme

>_JACK INTO YOUR DATABASE. GENERATE THE BACKEND. OWN THE GRID.

api-rest-generator parses a raw CREATE TABLE dump from MySQL, PostgreSQL, SQLite, or Microsoft SQL Server and emits a fully wired REST backend in your stack of choice. Auto-detects primary keys, foreign keys, and column types; maps SQL types to language-native ones; generates entities, controllers, DAOs / repositories, migrations, and module wiring. Two binaries ship from one crate: api-rest-generator for the full multi-target dispatch (Spring Boot Java / Kotlin / Groovy plus Rust/Loco), and loco-gen when you only want the Rust path. You feed it SQL. It feeds you a backend. No boilerplate. No hand-wiring.

Quickstart

Install from crates.io or build the two Rust binaries from source:

# install
cargo install api-rest-generator

# from source (Rust binaries)
git clone https://github.com/MenkeTechnologies/api-rest-generator
cd api-rest-generator
cargo build --release
# binaries land at target/release/api-rest-generator and target/release/loco-gen

# original Kotlin/Gradle pipeline still runs in parallel for parity reference
./gradlew build

There are two entry points with two different operating models:

  • api-rest-generatorconfig-driven, no CLI flags. It reads src/main/resources/config.properties (hard-coded relative path), parses the configured dump, and writes the generated tree under target.folder. This is the multi-target binary (Java / Kotlin / Groovy / Rust-Loco).
  • loco-genflag-driven, two subcommands (new / generate). Rust/Loco only.

The JVM / config-driven path. Edit src/main/resources/config.properties, then run the binary with no arguments:

# config.properties — the only knobs the main binary reads
target.folder=build/generated/
target.package=com/example/generated
file.name=mysql_dump.sql
target.language=java          # java | kotlin | groovy | rust-loco (alias: loco)
database.type=mysql           # mysql | postgresql | sqlite | mssql

# then, with the dump dropped under src/main/resources/:
cargo run --bin api-rest-generator     # or ./gradlew run for the Kotlin pipeline

The Rust/Loco path via the dedicated CLI (no config file needed):

# scaffold a fresh Loco project, generate, wire routes, run migrations
loco-gen new --ddl samples/pagila-postgresql.sql --name myapi \
             --out . --db postgresql --loco-db sqlite --wire --migrate

# emit into an EXISTING Loco project (no scaffold)
loco-gen generate --ddl samples/sakila-mysql.sql --out ./myapi --db mysql --wire

cd myapi && cargo loco start    # CRUD endpoints live at /api/{table_plural}/{id}

Full reference: README (section-by-section walkthrough of every config key, CLI flag, type-mapping rule, and output convention per target).

Input dialects — one card per parser path

MySQL

Standard CREATE TABLE grammar with backticked identifiers, inline PRIMARY KEY + table-level FOREIGN KEY constraints, MySQL-native types (tinyint, bigint, varchar, datetime, timestamp, bit).

PostgreSQL

pg_dump-compatible: handles CREATE TABLE + post-table ALTER TABLE ... ADD CONSTRAINT for PK/FK, PG-specific types (serial, text, boolean, numeric, real, integer).

SQLite

.dump-compatible: inline PKs, ignores PRAGMA / BEGIN / INSERT / CREATE INDEX noise, SQLite types (INTEGER, TEXT, REAL, NUMERIC, BLOB) with affinity rules.

MSSQL (SQL Server)

SSMS “Generate Scripts” output: [bracket] identifier stripping, ignored SET / GO control statements, MSSQL types (nvarchar, uniqueidentifier, money, datetime2).

Output targets — one card per backend

Java · Spring Boot + JPA

Lombok-powered (@Data, @AllArgsConstructor, @NoArgsConstructor). JPA entities with @Id, @ManyToOne, @JoinColumn. GenericDao service layer + Spring Data JPA repositories. REST controllers covering GET / POST / PUT / DELETE.

Kotlin · Spring Boot + JPA

Constructor injection, var properties with defaults, no Lombok. val / var distinguished by column nullability. Kotlin-native types (Int not Integer, Boolean not String). Same controller / DAO / repository surface as the Java target.

Groovy · Spring Boot + JPA

@Canonical annotation, field injection, no Lombok, no defaults. @Autowired wiring. Matches Java types but emits pure Groovy syntax.

Rust · Loco + Axum + SeaORM

SeaORM entities (#[derive(DeriveEntityModel)], #[sea_orm(primary_key)]). Axum-flavoured Loco controllers. Loco create_table migrations. Module aggregators wired into src/models/_entities/mod.rs and src/controllers/mod.rs. Raw-identifier escaping for Rust keywords (r#type).

4 × 4 matrix — every dialect feeds every target

Dialect ↓ / Target →JavaKotlinGroovyRust/Loco
MySQLOKOKOKOK
PostgreSQLOKOKOKOK
SQLiteOKOKOKOK
MSSQLOKOKOKOK

16 dialect × target combinations, each exercised by the test suite. Sample DDLs ship in samples/: chinook-sqlite.sql (15,902 lines), northwind-mssql.sql (9,350), pagila-postgresql.sql (1,842), sakila-mysql.sql (644) — a 27,738-line aggregate corpus of real-world schemas.

Type mapping — abridged

Full per-dialect tables (every SQL type → every target type) live in the README's TYPE MAPPING section. A short read across the most common cases — note that bit / boolean map to a real Boolean only on the Kotlin target; Java and Groovy emit String for those, mirroring the parser cascade in parser.rs:

SQLJavaKotlinGroovyRust (SeaORM)
int / integerIntegerIntIntegeri32
bigintLongLongLongi64
varchar / text / nvarcharStringStringStringString
boolean / bitStringBooleanStringbool
float / realFloatFloatFloatf32 / f64
double / numeric / moneyDoubleDoubleDoublef64
datetime / date (MSSQL)LocalDateLocalDateLocalDateDateTimeWithTimeZone
timestamp / datetime2LocalDateTimeLocalDateTimeLocalDateTimeDateTimeWithTimeZone
timeLocalTimeLocalTimeLocalTimeTime
uniqueidentifier / image / xmlStringStringStringString
blob / bytea / binaryStringStringStringVec<u8>

snake_case tables → PascalCase entities; column names go camelCase on the JVM, snake_case on Rust. Rust-keyword columns are escaped as raw identifiers (r#type). Primary keys default to Long (JVM) / PkAuto (Loco); foreign keys default to Integer (JVM) / i32 (Loco).

Generated REST shape (per table)

The two backend families wire slightly different route sets. The JVM Spring Boot targets prefix everything with GlobalConstants.CONTEXT_PATH (/api/v1) and add two collection-level verbs the Loco target does not:

# JVM (Spring Boot — Java / Kotlin / Groovy) — 7 routes per entity
GET    /api/v1/{entity}           # list all
GET    /api/v1/{entity}/{id}      # fetch one
POST   /api/v1/{entity}           # create
PUT    /api/v1/{entity}           # update (collection-level @PutMapping)
DELETE /api/v1/{entity}/{id}      # delete one
DELETE /api/v1/{entity}           # delete all (collection-level @DeleteMapping)

The Rust/Loco target wires the five id-scoped CRUD routes under a pluralized prefix (.prefix("api/{snake_table_plural}")) — no collection-level PUT or DELETE:

# Rust/Loco (Axum + SeaORM) — 5 routes per entity
GET    /api/{table_plural}        # list all
POST   /api/{table_plural}        # create (JSON body, PK omitted)
GET    /api/{table_plural}/{id}   # fetch one
PUT    /api/{table_plural}/{id}   # update
DELETE /api/{table_plural}/{id}   # remove

JPA repositories on the JVM extend JpaRepository<T, Long> via Spring Data — pagination, sorting, projection — for free; the Loco target exposes its shape through SeaORM entities + Axum handlers (list / get_one / add / update / remove). With --wire, loco-gen patches src/app.rs to register every controller (idempotent // BEGIN loco-gen routes block).

Configuration — config.properties (main binary)

The api-rest-generator binary (and the Kotlin ./gradlew run pipeline) read exactly five keys from src/main/resources/config.properties. There are no command-line flags; the file is the entire interface.

KeyValuesDefaultMeaning
target.folderany pathbuild/generated/src/main/<lang>/Output directory root. When absent, falls back to a language-specific default folder.
target.packagee.g. com/example/generated(empty)Java/Kotlin/Groovy package path, used as both the on-disk subtree and the package declaration. Ignored by the Rust/Loco target.
file.namee.g. mysql_dump.sql(empty)DDL source file, resolved against the resources dir (src/main/resources/).
target.languagejava / kotlin / groovy / rust-loco (alias loco)javaOutput target. Any unrecognised value (including bare rust) falls through to java.
database.typemysql / postgresql / sqlite / mssqlmysqlSelects the dialect-specific normalizer applied before parsing.

Generating a dump to feed it: mysqldump --no-data (MySQL), pg_dump --schema-only (PostgreSQL), sqlite3 database.db .dump (SQLite), or SSMS “Generate Scripts” / sqlcmd (MSSQL).

loco-gen CLI reference (Rust/Loco only)

loco-gen is flag-driven with two subcommands. new scaffolds a fresh Loco project (shells out to loco new) then generates into it; generate emits into an existing project without scaffolding.

SubcommandWhat it does
loco-gen newRuns loco new -n <name> --db <loco-db> --bg none --assets none, parses the DDL, writes all generated files into the new project, and (with --wire) patches src/app.rs to register every controller. With --migrate, runs cargo loco db migrate afterward.
loco-gen generateEmits the generated tree into an existing Loco project (no scaffold). --wire still patches src/app.rs. Useful for re-running after schema changes.
FlagDefaultApplies toMeaning
--ddl FILE / -drequiredbothPath to the SQL dump.
--name NAME / -nrequirednewProject / crate name (becomes the new directory name).
--out DIR / -o. (new); required for generatebothWhere to create (new) or find (generate) the project.
--db DIALECTmysqlbothDDL dialect: mysql / postgresql / sqlite / mssql.
--loco-db DBsqlitenewBacking DB baked into the new Loco project: sqlite or postgres.
--wireoffbothPatch src/app.rs to register every generated controller's routes (idempotent — rewrites the same // BEGIN loco-gen routes block in place).
--migrateoffnewRun cargo loco db migrate after generation.
--loco-binloconewPath to the upstream loco CLI binary.

Prerequisite for new: the upstream Loco CLI (cargo install loco). The route-merge is surgical — existing modules like controllers::home (shipped by the Loco scaffold) are preserved, and re-running generate --wire after a schema change does not duplicate routes.

Loco target — caveats (not yet implemented)

Drawn verbatim from the README's RUST/LOCO TARGET section — the generated Loco tree is a starting point, not a finished API:

  • FK relations are surfaced as plain i32 columns; the SeaORM Relation enum is generated empty. Add #[sea_orm(belongs_to = ...)] arms by hand for eager-loading.
  • No unique / nullable / default-value detection — every column is non-null in the migration. Tweak ColType::XXNull / XUniq / XWithDefault(...) as needed.
  • No pagination, search, or filter endpoints — only the five basic CRUD verbs.
  • Composite primary keys are collapsed to a synthetic id PkAuto column (Loco/SeaORM require a single PK); the original PK columns remain as regular fields.

Architecture in one screen (Rust path)

src/
├── bin/
│   ├── main.rs              # api-rest-generator entrypoint — multi-target dispatch
│   └── loco_gen.rs          # loco-gen entrypoint — Rust-only path
├── lib.rs                   # public API surface
├── parser.rs                # DDL → Vec<Entity> (port of utils/Util.kt)
├── entity.rs                # Entity + ColumnToField structs (port of dto/*.kt)
├── normalize.rs             # name normalization (snake_case ↔ PascalCase / camelCase)
├── config.rs                # config-file loading + per-language defaults
├── constants.rs             # SQL-type → target-type maps (4 dialects × 4 targets)
├── globals.rs               # mutable global state (compat with original Kotlin shape)
├── templates.rs             # .tmpl loader + {{placeholder}} renderer (JVM targets)
└── loco.rs                  # Rust/Loco codegen — SeaORM entities, Axum controllers, migrations
src/main/kotlin/com/jakobmenke/bootrestgenerator/   # original Kotlin/Gradle implementation
├── Main.kt
├── dto/                                 # Entity.kt + ColumnToField.kt
├── utils/                               # Util.kt + Configuration.kt + Globals.kt + EntityToRESTConstants.kt
└── templates/Templates.kt
samples/
├── chinook-sqlite.sql        # 15,902 lines
├── northwind-mssql.sql       #  9,350 lines
├── pagila-postgresql.sql     #  1,842 lines
└── sakila-mysql.sql          #    644 lines
tests/
├── loco_smoke.rs             # Rust/Loco target end-to-end
├── loco_collision_pins.rs    # snake-table collisions, duplicate emission, FK-clobber, silent-drop
├── loco_edge_cases.rs        # Loco codegen edge cases on boundary DDL inputs
├── loco_keyword_escape.rs    # Rust-keyword escaping in generated field names
├── normalize_units.rs        # name-normalization invariants
├── parity_smoke.rs           # output matches original Kotlin pipeline on shared fixtures
└── sample_ddls.rs            # all 4 sample DDLs round-trip cleanly

11 production .rs files (3,956 LOC counting the 2 bin/ entrypoints) currently shadow the Kotlin codebase (9,094 LOC across 36 .kt files — 8 main + 28 test). The Kotlin path is the parity reference; the Rust port mirrors module-for-module to keep the diff readable during transition.

License & usage

MIT licensed. Generated code is yours — no runtime dependency on api-rest-generator itself, just on the target framework you picked (Spring Boot for JVM targets, Loco / Axum / SeaORM for Rust).