NexusORM is a lightweight, high-performance, compiler-based ORM (Object-Relational Mapping) generator written entirely in modern C++20.
Rather than relying on heavy reflection mechanisms or brittle runtime macros, NexusORM uses a bespoke compiler architecture to parse a minimal Domain Specific Language (DSL) and automatically emit strongly-typed, dependency-free C++ structs, repositories, Eloquent-style query builders, and raw SQL schemas.
- Features
- Installation & Building
- ⚡ End-to-End Quickstart: Installation → Execution
- 🚀 For End-Users: Using NexusORM
- 🛠️ For Developers: Compiler Architecture
- Roadmap
- Blazing Fast: Generates zero-overhead C++ code. Queries execute natively via
libpqxx(PostgreSQL) or equivalents without reflection mappings. - Custom DSL: Define your schema cleanly using
.nexusfiles instead of bloated XML or annotations. - Enum Types: First-class
enumsupport in the DSL — maps to nativeENUMin SQL andenum classin C++ with automaticpqxxbinding. - Eloquent-Style Query Builder: Chainable methods like
.where(),.orWhere(),.orderBy(), and.limit(). - Live Migrations: Built-in CLI arguments (
--migrate,--force) to run generated SQL directly against your Database using your system's native clients. - Environment Aware: Automatic
.envparsing incorporated generated code for database connection pooling. - Smart Types & Nuances: Native mapping to
std::optionalfor nullable fields, and UPSERT default logic (ON CONFLICT DO UPDATE). - Colored CLI Output: Beautiful ANSI-colored terminal output for build steps, successes, warnings, and errors.
Tip
For a complete step-by-step guide including Ubuntu, Arch, macOS setup, CMake integration, and troubleshooting — see docs/INSTALL.md.
# 1. Clone
git clone https://github.com/imranakki/NexusORM.git
cd NexusORM
# 2. Install dependencies (Ubuntu/Debian)
sudo apt-get install build-essential cmake libpqxx-dev libpq-dev postgresql-client
# 3. Build
cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build --parallel
# 4. (Optional) Install globally
sudo cp ./build/nexus /usr/local/bin/nexusThis is the complete zero-to-running flow for using NexusORM on a fresh machine. Follow every step in order.
# Ubuntu / Debian
sudo apt-get update
sudo apt-get install -y \
build-essential cmake git \
libpqxx-dev libpq-dev \
postgresql-client \
libmysqlclient-dev default-mysql-clientTip
On Arch Linux: sudo pacman -S base-devel cmake git libpqxx postgresql-libs mysql-libs
On macOS: brew install cmake libpqxx postgresql mysql-client
git clone https://github.com/imranakki/NexusORM.git
cd NexusORM
cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build --parallel
# Confirm the binary works
./build/nexus versionsudo cp ./build/nexus /usr/local/bin/nexus
nexus version # works from any directory nowCreate a file called schema.nexus in your project directory:
enum Role { Admin, Editor, Viewer }
table User {
id: uuid primary key
username: string unique
email: string unique
bio: text null
role: Role
is_active: boolean
created_at: datetime
}
table Post {
id: uuid primary key
title: string index
body: text
views: int
}
# Targeting PostgreSQL
nexus generate schema.nexus --db=postgres --out=./
# Targeting MySQL
nexus generate schema.nexus --db=mysql --out=./This produces:
schema_postgres.sql(orschema_mysql.sql) — ready-to-run DDL (CREATE TABLE,CREATE TYPE, indexes)schema.h— self-contained C++ ORM header (zero dependencies exceptlibpqxx/libmysqlclient)
Create a .env file at your project root (NexusORM loads it automatically):
# PostgreSQL
PG_HOST=localhost
PG_PORT=5432
PG_USER=postgres
PG_PASS=secret
PG_NAME=myapp
# — OR — a single URL:
# DATABASE_URL=postgresql://postgres:secret@localhost:5432/myapp# Generate + apply in one shot
nexus migrate schema.nexus --db=postgres
# Force-drop existing tables first (development only!)
nexus migrate schema.nexus --db=postgres --forceNexusORM calls psql (or mysql) under the hood using the credentials from your .env. Passwords are always redacted in terminal output.
Create main.cpp:
#include "schema.h"
#include <iostream>
int main() {
// orm::Database::get() connects lazily using DATABASE_URL / PG_* env vars
orm::UserRepository users;
// Insert or update (UPSERT)
orm::User u;
u.setId("550e8400-e29b-41d4-a716-446655440000")
.setUsername("alice")
.setEmail("alice@example.com")
.setRole(orm::Role::Admin)
.setIs_active(true)
.setCreated_at("2026-01-01 00:00:00");
users.save(u);
// Query builder
auto admins = users.select()
.where("role", "'Admin'")
.where("is_active", "true")
.orderBy("username", "ASC")
.limit(50)
.get();
for (const auto& admin : admins)
std::cout << admin.username << "\n";
// Find by primary key
auto found = users.findById("550e8400-e29b-41d4-a716-446655440000");
std::cout << "Found: " << found.username << "\n";
// Delete
users.remove("550e8400-e29b-41d4-a716-446655440000");
return 0;
}Compile, linking against libpqxx and libpq:
# g++ directly
g++ main.cpp -lpqxx -lpq -std=c++17 -o my_app
# Run
./my_appNote
For MySQL targets, link against -lmysqlclient instead of -lpqxx -lpq.
Already have a live database? Skip writing a schema and let NexusORM generate it for you:
# PostgreSQL → generate schema.nexus + schema.h
nexus pull --db=postgres --url="postgresql://user:pass@localhost/mydb" --out=./
# MySQL
nexus pull --db=mysql --url="mysql://root:secret@localhost:3306/mydb" --out=./This introspects all tables and writes two files named after your database — e.g. for --db-name=myapp: myapp.nexus (editable DSL) and myapp.h (the C++ ORM header). No manual schema writing required.
The .nexus file is the single source of truth for your data model. NexusORM reads it and automatically generates SQL DDL scripts and a fully-typed C++ ORM header — you never write boilerplate again.
A .nexus file is made of two kinds of top-level declarations, always at file scope:
enum <EnumName> { Variant1, Variant2, … }
table <TableName> { <field declarations> }
Each field inside a table block follows this pattern:
<fieldName>: <type> [modifier …] // optional comment
Rules:
- Field names and table names are case-sensitive.
- A field may have zero, one, or two modifiers (e.g.,
string unique indexis valid;null primary keyis not). enumdeclarations must come before anytablethat references them.- Inline comments start with
//and can appear anywhere on a line. - Whitespace (spaces, tabs, newlines) is ignored — indentation is cosmetic only.
Every field must be assigned exactly one type:
| Nexus Type | What it means | PostgreSQL DDL | MySQL DDL | C++ type |
|---|---|---|---|---|
int |
Whole number | INTEGER |
INT |
int |
string |
Short text (≤ 255 chars) | VARCHAR(255) |
VARCHAR(255) |
std::string |
text |
Unlimited-length text | TEXT |
TEXT |
std::string |
boolean |
True / false flag | BOOLEAN |
BOOLEAN |
bool |
float |
32-bit floating point | REAL |
FLOAT |
float |
double |
64-bit floating point | DOUBLE PRECISION |
DOUBLE |
double |
datetime |
Date + time value | TIMESTAMP |
DATETIME |
std::string |
uuid |
128-bit unique identifier | UUID |
CHAR(36) |
std::string |
MyEnum |
Any enum declared in this file | CREATE TYPE … AS ENUM |
ENUM(…) |
enum class MyEnum |
Tip
Use uuid for primary keys. It avoids auto-increment collisions across distributed systems and maps cleanly to std::string in the generated C++ struct.
Modifiers are optional keywords placed after the type on the same line:
| Modifier | SQL effect | C++ effect |
|---|---|---|
primary key |
PRIMARY KEY constraint |
Field is used in findById(), save() UPSERT key |
unique |
UNIQUE constraint |
No special C++ change |
index |
CREATE INDEX on this column |
No special C++ change |
null |
Column is NULL-able (default is NOT NULL) |
Field type becomes std::optional<T> |
Rules for modifiers:
- Every
tablemust have exactly one field withprimary key. The semantic validator enforces this — it will error if you have zero or more than one. nullandprimary keycannot both appear on the same field (a primary key must always have a value).uniqueandindexcan be combined:string unique indexcreates both aUNIQUEconstraint and a separate index.- Modifier order does not matter:
string null uniqueandstring unique nullbehave identically.
Enums let you define a closed set of allowed values, enforced at the database level.
enum Status { Draft, Published, Archived }
enum Priority { Low, Medium, High, Critical }
- Declare enums at the top of your file, before any
table. - Variant names are PascalCase by convention (e.g.,
Admin, notadmin). - In PostgreSQL, NexusORM emits
CREATE TYPE "Status" AS ENUM ('Draft', 'Published', 'Archived')before theCREATE TABLEthat references it. - In MySQL, NexusORM inlines the variants directly:
ENUM('Draft', 'Published', 'Archived'). - In C++, each enum becomes an
enum classwith a companionto_string()helper for safe PostgreSQL binding viapqxx.
// ── Enum declarations (must come first) ───────────────────────────────────────
enum Role { Admin, Editor, Viewer }
enum Status { Draft, Published, Archived }
// ── Table definitions ──────────────────────────────────────────────────────────
table User {
id: uuid primary key // PK — used in findById / save UPSERT
username: string unique // UNIQUE constraint, VARCHAR(255)
email: string unique // No two users can share an email
bio: text null // Long text, nullable → std::optional<std::string>
role: Role // Enum field, no modifier needed
score: double // Non-null by default
weight: float null // Optional float
is_active: boolean // bool, NOT NULL
created_at: datetime // TIMESTAMP / DATETIME, NOT NULL
}
table Post {
id: uuid primary key
title: string index // Indexed for fast search, not unique
body: text
views: int
status: Status // Enum — Draft | Published | Archived
author_id: uuid index // Foreign-key pattern: index the FK column
}
table Tag {
id: uuid primary key
name: string unique index // Both unique constraint AND index
slug: string unique
}
| Output file | Contents |
|---|---|
schema_postgres.sql |
CREATE TYPE for each enum, CREATE TABLE with constraints and indexes |
schema_mysql.sql |
CREATE TABLE with inlined ENUM(…), INDEX, UNIQUE KEY |
schema.h |
C++ structs, enum class types, *Repository classes, query builder |
The semantic validator catches these mistakes before any code is generated:
| Error | Cause | Fix |
|---|---|---|
No primary key defined for table 'X' |
Table has no primary key field |
Add <field>: uuid primary key |
Unknown type 'MyEnum' |
Enum used in a table before being declared | Move the enum block to the top of the file |
Duplicate field 'name' in table 'X' |
Two fields share the same name | Rename one |
Duplicate table 'X' |
Two table blocks with the same name |
Rename one |
'null' cannot be combined with 'primary key' |
Invalid modifier combo | Remove null from the PK field |
The nexus binary uses a subcommand style interface:
nexus <command> [arguments] [flags]
| Command | Description |
|---|---|
generate <schema.nexus> |
Parse & compile .nexus → SQL + .h (no DB needed) |
migrate <schema.nexus> |
Same as generate, then apply SQL to a live database |
pull |
Connect to a live DB, reverse-engineer its schema into .nexus, then generate .h |
help |
Print full usage |
version |
Print version string |
# PostgreSQL
nexus generate schema.nexus --db=postgres --out=./
# MySQL
nexus generate schema.nexus --db=mysql --out=./output/
# Force drop & recreate existing tables
nexus generate schema.nexus --db=postgres --forceCredentials are resolved in this priority order:
CLI flags →
PG_*env vars (Postgres) →DB_*env vars (both) →DATABASE_URL
# Postgres — via .env
nexus migrate schema.nexus --db=postgres
# Postgres — explicit flags
nexus migrate schema.nexus --db=postgres --db-host=localhost --db-user=admin --db-pass=secret --db-name=mydb
# Postgres — connection URL
nexus migrate schema.nexus --db=postgres --url="postgresql://user:pass@localhost/db"
# MySQL — via URL
nexus migrate schema.nexus --db=mysql --url="mysql://root:secret@localhost:3306/mydb"
# Force drop & recreate
nexus migrate schema.nexus --db=postgres --forceConnect to an existing database, introspect its schema, and emit a .nexus file plus a ready-to-use C++ header — no DSL file needed.
# Postgres — via URL
nexus pull --db=postgres --url="postgresql://user:pass@localhost/mydb" --out=./
# Postgres — explicit credentials (output files named "mydb.*")
nexus pull --db=postgres --db-host=localhost --db-user=postgres --db-name=mydb
# MySQL — via URL (output files named "mydb.*")
nexus pull --db=mysql --url="mysql://root:secret@localhost:3306/mydb" --out=./
# MySQL — explicit credentials
nexus pull --db=mysql --db-host=localhost --db-user=root --db-name=mydb
# Override the output filename with --schema-name
nexus pull --db=postgres --db-name=mydb --schema-name=custom_nameOutput for nexus pull:
<db-name>.nexus— editable DSL file named after your database (e.g.myapp.nexus)<db-name>.h— ready-to-use C++ header with repositories and query builders
Note
The file base-name is automatically taken from your database name — from --db-name, or parsed from --url if --db-name is not given. Use --schema-name=<custom> to override it.
NexusORM reads your .env file automatically. Choose one of three credential styles:
| Style | Variables | Priority |
|---|---|---|
| URL | DATABASE_URL |
Lowest |
| Generic | DB_HOST DB_PORT DB_USER DB_PASS DB_NAME |
Middle |
| Postgres-specific | PG_HOST PG_PORT PG_USER PG_PASS PG_NAME |
Highest |
# .env — Postgres
PG_HOST=localhost
PG_PORT=5432
PG_USER=postgres
PG_PASS=secret
PG_NAME=mydb
# .env — MySQL
DB_HOST=localhost
DB_PORT=3306
DB_USER=root
DB_PASS=secret
DB_NAME=mydbNote
Passwords are always redacted (****) in terminal output. The actual credential is only sent to the database client process.
The generated schema.h header contains everything you need to interface with your database safely. All emitted code is placed inside the orm:: namespace.
When compiling your own C++ project that includes schema.h, you must link the corresponding database client library:
- For PostgreSQL (
--db=postgres): Add-lpqxx -lpqto your linker flags. - For MySQL (
--db=mysql): Add-lmysqlclientto your linker flags.
NexusORM automatically generates an orm::Database::get() singleton that connects to your database using the connection string defined in your .env file (DATABASE_URL). Connection singletons are lazily evaluated on the first query.
All generated structs feature chained setters:
#include "schema.h"
int main() {
orm::User user;
user.setId(1)
.setUsername("imranakki")
.setEmail("test@test.com")
.setIsActive(true);
}Every table generates a repository (e.g. UserRepository). When you call save(), NexusORM executes native UPSERTs meaning it inserts the record, or updates the fields if the Primary Key already exists!
orm::UserRepository repo;
repo.save(user); // Insterts into the database
user.setIsActive(false);
repo.save(user); // Smoothly updates the existing recordNexusORM gives you an infinitely chainable query builder for fetching data, wrapping PostgreSQL parameterized protections automatically. Initiate the builder via .select().
// 1. Chained queries returning std::vector
auto activeUsers = repo.select()
.where("isActive", "true")
.orWhere("username", "imranakki")
.orderBy("id", "DESC")
.limit(10)
.get();
// 2. Fetch a single record natively mapped to a struct
auto firstUser = repo.select().where("id", "1").first();
// 3. Raw condition fallbacks
auto rawConds = repo.select().whereRaw("\"id\" > 5 AND \"isActive\" = true").get();You can bypass the structured builder completely while retaining automatic native mapping of the struct objects:
auto explicitUsers = repo.query("SELECT * FROM \"User\" WHERE \"id\" > 10 LIMIT 5");If you want to contribute, NexusORM is written in pure C++20 standard library (no external AST tools like ANTLR or Bison used).
Important
Before writing any code, please read CONTRIBUTING.md. It documents the project structure, the pipeline in detail, and — critically — why src/pqxx_abi_shim.cpp exists and must not be removed. Removing it will break the build on Ubuntu 22.04 and other distros shipping an older libpqxx.
- Lexical Analysis (
src/lexer/): Reads the.nexusfile character-by-character, discarding whitespace/comments, and constructs a normalizedstd::vector<Token>. - Parser (
src/parser/): A hand-written Recursive Descent LL(1) parser evaluates the tokens into an Abstract Syntax Tree (AST). The AST classes (SchemaNode,TableNode,FieldNode) represent the graph memory. - Semantic Validator (
src/semantic/): Traverses the AST before generation to catch business-logic errors (e.g., duplicate tables/fields, missing primary keys, invalid modifier combinations). - Code Generators (
src/generator/): Takes the validated AST and synthesizes raw string outputs:CppGenerator: Emitsschema.h(Structs, Repositories, QueryBuilders,libpqxxwrappers).PostgresGenerator/MySqlGenerator: Emits dialect-specific.sqlscripts.GeneratorContext: Acts as the factory distributing AST pieces to the relevant generator strategy.
- Introspection (
src/introspect/): Fornexus pull— connects to a live DB vialibpq(PostgreSQL C API) orlibmysqlclient(MySQL C API), reverse-engineers the schema into an AST, then passes it throughNexusWriter(emits.nexus) andCppGenerator(emits.h).
See CONTRIBUTING.md for:
- Full annotated project directory layout
- In-depth explanation of
src/pqxx_abi_shim.cpp(the libpqxx ABI compatibility shim) - CMake target reference and library dependency breakdown
- Step-by-step contribution workflow
NexusORM is heavily unit-tested. To run the Internal Compiler Test Suites:
cd build
./lexer_tests # Tokenization validations
./parser_tests # AST generation validations
./validator_tests # Semantic rule validations
./ast_tests # E2E Code Generation validations- Relationships Engine: Introduce
1-to-manyandmany-to-manyDSL keywords to emit SQLFOREIGN KEYcascades and generated C++ methods like.with("relation")for eager-loading. - AST-Diff Migrations: Upgrade the SQL generators from emitting complete
CREATE / DROPqueries to evaluating AST snapshots and emitting minimal, preciseALTER TABLEexecution queries. - Driver Expansions: Include code generators for SQLite bindings and asynchronous networking database drivers in C++.