README.md

June 24, 2026 · View on GitHub

    _    ____ ___   ____  _____ ____ _____
   / \  |  _ \_ _| |  _ \| ____/ ___|_   _|
  / _ \ | |_) | |  | |_) |  _| \___ \ | |
 / ___ \|  __/| |  |  _ <| |___ ___) || |
/_/   \_\_|  |___| |_| \_\_____|____/ |_|

  ____ _____ _   _ _____ ____      _  _____ ___  ____
 / ___| ____| \ | | ____|  _ \    / \|_   _/ _ \|  _ \
| |  _|  _| |  \| |  _| | |_) |  / _ \ | || | | | |_) |
| |_| | |___| |\  | |___|  _ <  / ___ \| || |_| |  _ <
 \____|_____|_| \_|_____|_| \_\/_/   \_\_| \___/|_| \_\

// JACK INTO YOUR DATABASE. GENERATE THE BACKEND. OWN THE GRID.

Read the Docs · Engineering Report


CI

> SYSTEM OVERVIEW

API REST Generator is a zero-config code generation engine that parses raw MySQL, PostgreSQL, SQLite, or Microsoft SQL Server DDL dumps and outputs a fully wired REST backend in your stack of choice — Spring Boot (Java/Kotlin/Groovy) or Loco (Rust/Axum/SeaORM) — entities, controllers, DAOs/repositories or migrations, all of it. You feed it SQL. It feeds you a backend.

No boilerplate. No hand-wiring. Just schema in, API out.


> CAPABILITIES

[x] Parse MySQL CREATE TABLE statements
[x] Parse PostgreSQL CREATE TABLE + ALTER TABLE statements (pg_dump compatible)
[x] Parse SQLite CREATE TABLE statements (.dump compatible)
[x] Parse MSSQL CREATE TABLE statements (SSMS Generate Scripts compatible)
[x] Auto-detect primary keys, foreign keys, column types
[x] Generate JPA entities with @Id, @ManyToOne, @JoinColumn
[x] Generate SeaORM entities (`#[derive(DeriveEntityModel)]`, `#[sea_orm(primary_key)]`)
[x] Generate REST controllers (GET / POST / PUT / DELETE)
[x] Generate DAO service layer with GenericDao pattern (JVM targets)
[x] Generate Spring Data JPA repositories (JVM targets)
[x] Generate Loco controllers + migrations + module aggregators (Rust target)
[x] Output in Java, Kotlin, Groovy, **or Rust/Loco**
[x] Map MySQL types --> Java/Kotlin/Groovy/Rust types (varchar->String, bigint->Long/i64, datetime->LocalDate/DateTimeWithTimeZone, ...)
[x] Map PostgreSQL types --> Java/Kotlin/Groovy/Rust types (integer, text, boolean, serial, numeric, real, ...)
[x] Map SQLite types --> Java/Kotlin/Groovy/Rust types (INTEGER, TEXT, REAL, NUMERIC, BLOB, ...)
[x] Map MSSQL types --> Java/Kotlin/Groovy/Rust types (nvarchar, uniqueidentifier, money, datetime2, ...)
[x] Java: Lombok-powered (@Data, @AllArgsConstructor, @NoArgsConstructor)
[x] Kotlin: Constructor injection, var properties with defaults, no Lombok
[x] Groovy: @Canonical annotation, field injection, no Lombok
[x] Rust/Loco: SeaORM entities, Axum-flavoured controllers, Loco `create_table` migrations, raw-identifier escaping for Rust keywords (`r#type`)
[x] snake_case tables --> PascalCase entities, camelCase (JVM) / snake_case (Rust) fields
[x] Template-driven codegen with {{placeholder}} substitution (JVM targets)

> TECH STACK

LayerTech
LanguageKotlin 2.3.20
RuntimeJava 25
FrameworkSpring Boot 4.0.4
ORMSpring Data JPA (Jakarta)
BuildGradle 9.4.1 (Kotlin DSL)
BoilerplateLombok (Java), @Canonical (Groovy)
TestsJUnit 5
DB SupportMySQL, PostgreSQL, SQLite, MSSQL
Output LanguagesJava, Kotlin, Groovy, Rust/Loco

> QUICKSTART

1. Configure

Edit src/main/resources/config.properties:

target.folder=/absolute/path/to/your/project/src/main/java/
target.package=com/your/package
file.name=mysql_dump.sql
target.language=kotlin
database.type=mysql

Set target.language to java, kotlin, groovy, or rust-loco to control the generated output language. Default is java.

When target.language=rust-loco, the generator emits a Loco project tree (SeaORM entities, Axum-style controllers, loco_rs::schema::create_table migrations, module aggregators, and an app_routes.rs snippet) under target.folder — see the RUST/LOCO TARGET section below.

Set database.type to mysql, postgresql, sqlite, or mssql to match your dump file format. Default is mysql.

2. Drop your SQL

Place your DDL dump file in src/main/resources/.

For MySQL, use mysqldump --no-data to generate the dump file. For PostgreSQL, use pg_dump --schema-only to generate the dump file. For SQLite, use sqlite3 database.db .dump to generate the dump file. For MSSQL, use SSMS "Generate Scripts" or sqlcmd to export the schema.

3. Execute

./gradlew run

Or run Main.kt from your IDE. Watch the grid light up.

./gradlew run covers the JVM output languages (java / kotlin / groovy). For target.language=rust-loco, run the Rust generator binary instead — it reads the same config.properties:

cargo run --bin api-rest-generator

> OUTPUT MATRIX

File extensions depend on target.language: .java, .kt, .groovy, or .rs.

The directory tree below applies to the JVM targets (java/kotlin/groovy). For rust-loco, see the RUST/LOCO TARGET section below for the Loco-flavoured layout.

{target.folder}/{target.package}/
 |-- entity/
 |    |-- User.{ext}           @Entity @Table @Column @Id @ManyToOne
 |    |-- Module.{ext}
 |    \-- ...
 |-- rest/
 |    |-- UserResource.{ext}   @RestController with full CRUD
 |    |-- ModuleResource.{ext}
 |    \-- ...
 |-- dao/
 |    |-- UserDao.{ext}        @Service implementing GenericDao<T>
 |    |-- GenericDao.{ext}     Generic interface for all DAOs
 |    \-- ...
 |-- repository/
 |    |-- UserRepository.{ext} extends JpaRepository<T, Long>
 |    \-- ...
 \-- utils/
      \-- GlobalConstants.{ext}

Language Differences

FeatureJavaKotlinGroovyRust/Loco
Entity boilerplateLombok (@Data, @NoArgsConstructor)var properties with defaults@Canonical#[derive(DeriveEntityModel)] (SeaORM)
DI style@Autowired field injectionConstructor injection@Autowired field injectionState<AppContext> extractor
Inheritance syntaxextends / implements: (colon)extends / implementstrait impl
File extension.java.kt.groovy.rs
SemicolonsYesNoNoYes
int typeIntegerIntIntegeri32
bigint typeLongLongLongi64
bit/boolean typeStringBooleanStringbool
FK typeIntegerIntIntegeri32
datetime / timestampLocalDate / LocalDateTimeLocalDate / LocalDateTimeLocalDate / LocalDateTimeDateTimeWithTimeZone (SeaORM)

> RUST/LOCO TARGET

target.language=rust-loco (alias loco) skips the JVM template tree entirely and emits a Loco-flavoured Rust project tree under target.folder — drop it into a loco new skeleton and you have a working REST API in two steps.

Layout

{target.folder}/
 |-- src/
 |    |-- models/
 |    |    |-- mod.rs                            # aggregator
 |    |    |-- _entities/
 |    |    |    |-- mod.rs
 |    |    |    |-- prelude.rs
 |    |    |    \-- {snake_table}.rs              # SeaORM Model + Relation
 |    |    \-- {snake_table}.rs                   # ActiveModelBehavior + impls
 |    |-- controllers/
 |    |    |-- mod.rs
 |    |    \-- {snake_table}.rs                   # full CRUD (list/get/add/update/remove)
 |    \-- app_routes.rs                            # snippet for your Hooks::routes
 \-- migration/
      \-- src/
           |-- lib.rs                              # Migrator with every migration
           \-- m{datetime}_{snake_table}.rs        # loco_rs::schema::create_table

Type mapping (Rust/Loco target)

SQL typeRust (SeaORM)Loco ColType
bigint, int8, bigseriali64BigInteger
int, integer, int4, seriali32Integer
smallint, int2i16SmallInteger
tinyinti8SmallInteger
varchar(n), char, text, nvarcharStringString
bool, boolean, bitboolBoolean
float, realf32Float
double, double precisionf64Double
decimal, numeric, moneyf64Double
dateDateDate
timeTimeTime
datetime, datetime2, timestamp(tz)DateTimeWithTimeZoneTimestampWithTimeZone
blob, bytea, binary, varbinaryVec<u8>Blob
primary key column (any)i32PkAuto

Reserved Rust keywords (e.g. column named type, match, move) are emitted as raw identifiers (r#type) so the generated structs compile without losing the original column name in JSON serialisation.

The repo ships a dedicated Rust CLI binary, loco-gen, that scaffolds a Loco project AND emits all entities, controllers, and migrations from a DDL dump in one shot — no config file editing required.

# build the CLI once
cargo install --path . --bin loco-gen      # or: cargo build --release && cp target/release/loco-gen ~/bin/

# prerequisite: the upstream Loco CLI
cargo install loco

# one-shot: scaffold + generate + wire routes + run migrations
loco-gen new \
  --ddl  ./mysql_dump.sql \
  --name myapi \
  --out  . \
  --db   mysql \
  --loco-db sqlite \
  --wire \
  --migrate

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

Subcommands:

SubcommandWhat it does
loco-gen newRuns loco new -n <name> --db <loco-db>, parses the DDL, writes all generated files into the new project, and (with --wire) patches src/app.rs to register every controller's routes. Optional --migrate runs cargo loco db migrate.
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.

Flags:

FlagDefaultMeaning
--ddl FILEreq'dPath to your SQL dump.
--name NAMEreq'd (new)Project / crate name.
--out DIR.Where to create / find the project.
--db DIALECTmysqlDDL dialect: mysql, postgresql, sqlite, mssql.
--loco-db DBsqliteBacking DB for the new Loco project: sqlite, postgres.
--wireoffPatch src/app.rs to register every generated controller's routes (idempotent — re-running replaces the same // BEGIN loco-gen routes block).
--migrateoffRun cargo loco db migrate after generation.
--loco-binlocoPath to the upstream loco CLI binary.

The route-merge is idempotent and surgical: existing modules like controllers::home (shipped with the Loco scaffold) are preserved. Re-running loco-gen generate --wire after a schema change does not duplicate routes — the // BEGIN loco-gen routes ... // END loco-gen routes block is rewritten in place.

Wiring manually (advanced)

If you prefer the original config-file workflow (run the JVM-style generator binary instead of the CLI):

loco new -n myapi --db sqlite --bg none --assets none
cd myapi

# point the generator at this project
cat > /path/to/api-rest-generator/src/main/resources/config.properties <<EOF
target.folder=$(pwd)
target.package=
file.name=mysql_dump.sql
target.language=rust-loco
database.type=mysql
EOF

# (back in the generator repo)
cargo run --release --bin api-rest-generator

# back in the loco project
cargo loco db migrate
cargo loco start

You'll then need to add the generated routes to your Hooks::routes impl in src/app.rs by hand:

fn routes(_ctx: &AppContext) -> AppRoutes {
    AppRoutes::with_default_routes()
        .add_route(controllers::home::routes())
        .add_route(controllers::users::routes())
        .add_route(controllers::orders::routes())
        // ... one per generated entity
}

(The generator drops a ready-made register_generated_routes(routes) helper in src/app_routes.rs you can call instead. The loco-gen CLI does this automatically when --wire is passed.)

Generated REST shape per table

GET     /api/{snake_table_plural}             # list all
POST    /api/{snake_table_plural}             # create (JSON body, PK omitted)
GET     /api/{snake_table_plural}/{id}        # fetch one
PUT     /api/{snake_table_plural}/{id}        # update
DELETE  /api/{snake_table_plural}/{id}        # remove

Not yet implemented

  • FK relations are surfaced as plain i32 columns; the SeaORM Relation enum is generated empty. Add #[sea_orm(belongs_to = ...)] arms by hand if you need eager-loading.
  • No unique / nullable / default-value detection — every column is non-null in the migration. Tweak the generated ColType::XXNull / XUniq / XWithDefault(...) as needed.
  • No pagination, search, or filter endpoints — only the five basic CRUD verbs above.
  • 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 — enforce uniqueness via a partial index in a follow-up migration if needed.

Verified sample DDLs

loco-gen is exercised against four real-world open-source schemas living under samples/. Each one round-trips through scaffold → generate → cargo buildcargo loco db migrate → live CRUD on localhost:5150.

SampleSource--dbTablesNotes
SakilajOOQ/sakilamysql16DVD rental store; exercises triggers/views/functions filtering.
Chinooklerocha/chinook-databasesqlite11Digital media store; [bracketed] identifiers + composite PKs.
Pagiladevrimgunduz/pagilapostgresql22Postgres Sakila port; ALTER TABLE ADD PRIMARY KEY style.
Northwindmicrosoft/sql-server-samplesmssql13Classic MSSQL; double-quoted identifiers + Order Details space-in-name.

Reproduce locally:

cargo build --release
for s in sakila:mysql chinook:sqlite pagila:postgresql northwind:mssql; do
    name=${s%%:*}; db=${s##*:}
    ./target/release/loco-gen new --ddl samples/${name}-*.sql \
        --name ${name}_api --out /tmp/samples --db $db --loco-db sqlite --wire
    ( cd /tmp/samples/${name}_api && cargo build && cargo loco db migrate )
done

The integration test tests/sample_ddls.rs pins the parse output (entity counts, PK presence, no leaked quotes/brackets/tabs) so regressions in the normalizer/parser are caught in CI without needing the Loco toolchain installed.


> GENERATED ENDPOINTS (JVM targets)

Every entity gets a full CRUD interface wired to /api/v1 (Spring Boot java/kotlin/groovy targets):

GET     /api/v1/{entity}        // pull all records
GET     /api/v1/{entity}/{id}   // pull one by id
POST    /api/v1/{entity}        // create
PUT     /api/v1/{entity}        // update
DELETE  /api/v1/{entity}/{id}   // flatline one
DELETE  /api/v1/{entity}        // flatline all

For the rust-loco target the generated controller wires only the 5 id-scoped CRUD routes — GET /, POST /, GET /{id}, PUT /{id}, DELETE /{id} (no collection-level PUT or DELETE) — see the RUST/LOCO TARGET section.


> TYPE MAPPING

MySQL

MySQL              -->   Java/Kotlin/Groovy
-----------------------------------------
int, tinyint       -->   Integer
bigint             -->   Long
varchar            -->   String
float              -->   Float
double             -->   Double
datetime           -->   LocalDate
timestamp          -->   LocalDateTime
time               -->   LocalTime
bit                -->   String
PRIMARY KEY        -->   Long (default)
FOREIGN KEY        -->   Integer (default)

PostgreSQL

PostgreSQL                    -->   Java/Kotlin/Groovy
-------------------------------------------------
integer, smallint, serial     -->   Integer
bigint, bigserial             -->   Long
character varying, varchar    -->   String
text                          -->   String
real                          -->   Float
double precision              -->   Double
numeric                       -->   Double
boolean, bool                 -->   String (Java/Groovy) / Boolean (Kotlin)
date                          -->   LocalDate
timestamp (with/without tz)   -->   LocalDateTime
time (with/without tz)        -->   LocalTime
PRIMARY KEY                   -->   Long (default)
FOREIGN KEY                   -->   Integer (default)

SQLite

SQLite               -->   Java/Kotlin/Groovy
-----------------------------------------
INTEGER              -->   Integer
TEXT                 -->   String
VARCHAR(n)           -->   String
REAL                 -->   Float
NUMERIC              -->   Double
BLOB                 -->   String
INTEGER PRIMARY KEY  -->   Long (inline PK detected)
FOREIGN KEY          -->   Integer (default)

MSSQL (SQL Server)

MSSQL                         -->   Java/Kotlin/Groovy
-------------------------------------------------
int, tinyint, smallint        -->   Integer
bigint                        -->   Long
nvarchar, nchar, varchar      -->   String
ntext, text, char             -->   String
uniqueidentifier              -->   String
image, xml                    -->   String
float                         -->   Float
real                          -->   Float
money, smallmoney             -->   Double
decimal, numeric              -->   Double
bit                           -->   String (Java/Groovy) / Boolean (Kotlin)
date                          -->   LocalDate
datetime                      -->   LocalDate
datetime2, datetimeoffset     -->   LocalDateTime
smalldatetime                 -->   LocalDateTime
time                          -->   LocalTime
PRIMARY KEY                   -->   Long (default)
FOREIGN KEY                   -->   Integer (default)

> ARCHITECTURE

 mysql_dump.sql
    |
    v
 [ TOKENIZER ] -- strips comments (#, --), splits tokens     (Util.getWords)
    |
    v
 [ NORMALIZER ] -- combines multi-word types, strips          (Util.normalizePostgresqlWords)
    |               punctuation, brackets, filters noise       (Util.normalizeSqliteWords)
    |               (database-specific normalizer per type)    (Util.normalizeMssqlWords)
    v
 [ PARSER ] -- detects CREATE TABLE, columns, keys           (Util.parseWords)
    |           supports ALTER TABLE for PG primary/foreign keys
    |
    v
 [ TEMPLATE ENGINE ] -- fills {{entityName}}, {{fields}}, .. (Templates.kt)
    |
    v
 [ FILE WRITER ] -- outputs layered Spring Boot project      (Main.kt)

> PROJECT STRUCTURE

src/main/kotlin/com/jakobmenke/bootrestgenerator/
 |-- Main.kt                          Entry point & file writer
 |-- dto/
 |    |-- Entity.kt                   Entity data model
 |    \-- ColumnToField.kt            Column-to-field mapping
 |-- templates/
 |    \-- Templates.kt                Template engine & replacements
 \-- utils/
      |-- Configuration.kt            Config reader
      |-- Util.kt                     Parser, type mapper, key detector
      |-- EntityToRESTConstants.kt    Regex patterns & constants
      \-- Globals.kt                  Global state holder

src/main/resources/templates/
 |-- *.tmpl                              Java templates (default)
 |-- kotlin/*.tmpl                       Kotlin templates
 \-- groovy/*.tmpl                       Groovy templates

src/                                     Rust crate (port of the Kotlin generator + Loco target)
 |-- lib.rs                              Crate root
 |-- config.rs                           config.properties reader
 |-- constants.rs                        Regex patterns & constants
 |-- entity.rs                           Entity / column models
 |-- globals.rs                          Global state holder
 |-- normalize.rs                        PG/SQLite/MSSQL normalizers
 |-- parser.rs                           Tokenizer & CREATE/ALTER TABLE parser
 |-- templates.rs                        JVM template engine
 |-- loco.rs                             Loco/SeaORM emitter
 \-- bin/
      |-- main.rs                        api-rest-generator binary (config-driven)
      \-- loco_gen.rs                    loco-gen CLI (clap)

> RUNNING TESTS

./gradlew test    # JVM suite (parser, type mappers, templates, pipelines)
cargo test        # Rust suite (parser/normalizer parity, Loco emitter, sample DDLs)

Test Suite Overview

Unit, template, and integration tests cover the parser, type mappers, template engine, and full pipeline across all supported database / language combinations.

CategoryTest ClassWhat It Covers
UnitMainTestPK/FK identification, string capitalization, camelCase conversion
UnitUtilTestfirstLetterToCaps, camelName, getId, getWords, parseWords
UnitKotlinUtilTestKotlin type mapping (Int vs Integer, Boolean vs String), PK/FK types
UnitGroovyUtilTestGroovy type mapping (matches Java types), cross-database type handling
UnitColumnToFieldTestColumnToField data class constructors, equality, copy, mutability
UnitEntityTestEntity data class constructors, properties, column management
UnitEntityToRESTConstantsTestRegex patterns for all SQL types, PK/FK parsing, MySQL/PG/SQLite/MSSQL type regexes
UnitEdgeCaseParsingTestEmpty input, special characters, self-referencing FKs, large names, multi-table edge cases
UnitConfigurationTestConfig file loading, language-specific default folders, fallback behavior
UnitKotlinConfigurationTestKotlin language property, global flags, case-insensitive matching
UnitGroovyConfigurationTestGroovy language property, global flags, mutual exclusion with Kotlin
TemplateTemplatesTestJava templates: entities, DAOs, repositories, REST resources, package declarations
TemplateKotlinTemplatesTestKotlin templates: colon inheritance, constructor injection, val/var, no Lombok
TemplateGroovyTemplatesTestGroovy templates: @Canonical, @Autowired, implements, no Lombok
TemplateKotlinEntityFieldDefaultsTestKotlin field defaults ("", 0, 0L, false), nullable types, Java mode guard
TemplateGroovyEntityFieldsTestGroovy field declarations without defaults or nullable types, language comparison
TemplateRestRepositoryTemplateTest@RepositoryRestResource annotation, Spring Data REST imports
IntegrationFullPipelineTestEnd-to-end MySQL: parse 18 entities, validate columns/keys/types, write all files
IntegrationPostgresqlPipelineTestPostgreSQL pipeline: ALTER TABLE constraints, PG type mappings, cross-language
IntegrationSqlitePipelineTestSQLite pipeline: inline PKs, .dump noise filtering (INSERT, PRAGMA, BEGIN)
IntegrationMssqlPipelineTestMSSQL pipeline: [bracket] stripping, SET/GO filtering, MSSQL type mappings
IntegrationKotlinPipelineTestKotlin pipeline: type mapping, .kt file generation, template content
IntegrationGroovyPipelineTestGroovy pipeline: type mapping, .groovy file generation, @Canonical templates
IntegrationCustomSqlParsingTestComplex SQL: mixed comments, multi-FK tables, every data type, end-to-end template gen
IntegrationEmptyEntityListTestEmpty entity lists, single entity generation, deep package paths
IntegrationKotlinContentValidationTestGenerated Kotlin files: no Java artifacts, no placeholders, correct idioms
IntegrationGroovyContentValidationTestGenerated Groovy files: no Java/Kotlin artifacts, pure Groovy syntax
IntegrationCrossDatabaseLanguageTestAll db/language combos (databases x languages), parameterized matrix

Coverage Matrix

              MySQL   PostgreSQL   SQLite   MSSQL
           +--------+------------+--------+--------+
Java       |   ✓    |     ✓      |   ✓    |   ✓    |
Kotlin     |   ✓    |     ✓      |   ✓    |   ✓    |
Groovy     |   ✓    |     ✓      |   ✓    |   ✓    |
Rust/Loco  |   ✓    |     ✓      |   ✓    |   ✓    |
           +--------+------------+--------+--------+

// CREATED BY MENKETECHNOLOGIES