Database
TypeORM
Introduction
JCC Express MVC supports TypeORM as an ORM option via DB_ORM=typeorm. TypeORM uses decorator-based entities in app/Entities/ — separate from JCC Eloquent models in app/Models/.
You can also use TypeORM alongside JCC Eloquent by keeping DB_ORM=jcc and configuring database.typeorm.dataSource in app/Config/database.ts (the framework still registers a singleton DataSource).
Enable TypeORM
Option A — TypeORM as primary ORM
DB_CONNECTION is mapped to a TypeORM driver:
DB_CONNECTION | TypeORM type |
|---|---|
mysql2, mysql, mariadb | mysql |
postgres, postgresql, pg | postgres |
sqlite, better-sqlite3 | better-sqlite3 |
Option B — TypeORM alongside JCC Eloquent
Keep DB_ORM=jcc for Knex/JCC models and ensure app/Config/database.ts includes:
TypeORMServiceProvider registers the client whenever database.typeorm.dataSource is set.
Required packages
Install the driver for your database (already common in JCC apps):
App DataSource class
app/Services/TypeORMDataSource.ts extends TypeORM DataSource and reads connection settings from DB_* env vars:
Wire it in app/Config/database.ts:
When the driver connects, it scans app/Entities/ for .ts/.js files and registers exported entity classes (unless autoloadEntities: false or you pass entities explicitly).
Define entities
Place entities in app/Entities/ with TypeORM decorators (@Entity, @Column, relations, etc.). See Extend BaseEntity for the recommended User entity shape used in this project.
Generate a new entity with ArtisanNode:
When DB_ORM=typeorm, make:model scaffolds into app/Entities/ with TypeORM decorators and extends BaseEntity.
Extend BaseEntity (recommended)
Entities do not have to extend TypeORM's BaseEntity — a plain @Entity() class works with getRepository(Entity). Extending BaseEntity is recommended when you want:
- Active Record-style static methods —
find,findOne,findOneBy,save,remove, and related helpers on the class itself. - Route model binding — the framework resolves route parameters into loaded entity instances when your controller action is typed with the entity class (see below and Routing).
Example (app/Entities/User.ts):
Static methods
With BaseEntity, query without injecting the DataSource in every call:
You can still use typeorm().getRepository(User) or constructor-injected TypeORMDataSource when you prefer the repository pattern.
Static methods and the data source: TypeORM's
BaseEntitystatic helpers need a data source on the entity class. After the app boots, callUser.useDataSource(typeorm())once per entity (for example inAppServiceProvider.boot()). If you skip that step, usetypeorm().getRepository(User)instead — route model binding still works either way.
Route model binding
Register a route whose parameter name matches the camelCase entity name (User → {user}):
Type-hint the entity on a @Method() action. The framework loads the record via the TypeORM repository (findOne by id, or by column with {column$user} syntax):
Binding by email (or any column):
The resolved value is a BaseEntity instance, so you can call instance methods (save(), remove(), etc.) directly in the action.
Using TypeORM in controllers
Constructor injection (recommended):
Global helper:
Auth and route model binding
When DB_ORM=typeorm, framework helpers resolve users and route-bound models via TypeORM repositories:
findUserById,findUserForAuthresolveModelBinding— implicit{user}/:userbinding and{column$user}column bindingAuthMiddlewareAPI auth lookup
getModel("User") loads from app/Entities/User instead of app/Models/.
For route model binding, extend BaseEntity on your entity class and type-hint the entity in @Method() actions (see Extend BaseEntity above). The demo route GET /api/typeorm/{user} in this repo uses that pattern.
Migrations (TypeORM CLI)
Use the TypeORM CLI directly for migrations (not yet wrapped in ArtisanNode):
For local prototyping only, TYPEORM_SYNCHRONIZE=true auto-syncs schema from entities. Do not use synchronize in production.
How the framework registers TypeORM
When DB_ORM=typeorm (or when database.typeorm.dataSource is set in app/Config/database.ts):
- Your
TypeORMDataSourceis registered as a singleton. - Entities in
app/Entities/are autoloaded on connect (unless disabled in config). - The data source is available via constructor injection,
app.resolve("typeorm"), or the globaltypeorm()helper. - Knex / Sequelize / Mongoose setup is skipped so TypeORM owns the connection.
- Prisma and TypeORM do not register together when one is the primary
DB_ORM. - The database monitor query collector is Knex-only; it is skipped when
DB_ORMis notjcc.
Demo routes
This repo includes TestTypeORMController with routes under /api/typeorm when TypeORM is configured:
GET /api/typeorm— list users with postsPOST /api/typeorm— create sample user and postGET /api/typeorm/{user}— show one user via route model binding