JCC Eloquent ORM
Defining Model
Introduction
In JCC-Eloquent, each model is a class that extends Model and represents a table record.
Basic rule:
- class name -> table name (plural snake_case by default)
- model properties control behavior (hidden fields, timestamps, soft deletes, mass assignment, etc.)
Generate a model
Use ArtisanNode instead of creating files manually:
Generated models should live in app/Models.
Basic model definition
By convention this maps to users table.
Customizing table and primary key
Mass assignment control
Use fillable / guarded to control fill(...) and create/update assignments:
Hiding attributes in JSON
toJSON() will omit hidden fields.
TypeScript property declarations (declare)
JCC Eloquent hydrates model instances at runtime — properties like name, email, and id are assigned dynamically from the database result after a query. TypeScript doesn't know about these properties by default, so accessing user.name would produce a type error.
The solution is the declare keyword. It tells TypeScript that a property will exist at runtime (injected by the ORM) without emitting any JavaScript or initializing the value:
With these declarations in place, TypeScript fully understands the model's shape:
Why declare and not a normal property?
A regular property definition like name: string = "" would initialize the property to "" in the constructor and overwrite whatever the ORM hydrated. declare is purely a compile-time annotation — it emits no JavaScript, so the ORM's runtime assignment is never touched.
Why ? (optional)?
A freshly instantiated model (before any query) has none of these properties set, so marking them optional (name?: string) is accurate. It also matches the ORM's behavior when a column is nullable or when the model was created without all fields.
Recommended pattern
Declare every column your table has. This gives you full type safety across controllers, services, and views without any runtime cost:
Accessors and mutators
Define getter/setter style methods:
Casts and timestamps behavior
Model/base supports static casts and timestamp handling:
Default model behavior:
timestampsenabledprimaryKeyisidsoftDeletesdisabled unless enabled in model strategy
Defining relationships in model
Available relationship helpers include hasOne, hasMany, belongsTo, belongsToMany, and morph variants.
Model events and hooks
Register lifecycle callbacks on the model with static boot(). The framework calls boot() automatically the first time the model participates in an event.
Use this.<event>(callback) inside boot() for each hook you need — omit events you do not use.
To skip events for a single operation, use saveQuietly(), executeWithoutEvents(), and related helpers. For observer classes and full event reference, see Observer.
API tokens (createToken)
Models can issue JWT access tokens for API authentication. This is opt-in: set protected hasToken = true on the model class. Without it, calling createToken() throws ModelTokenError.
Issuing a token
Call createToken() on a loaded model instance:
| Argument | Type | Default | Description |
|---|---|---|---|
payload | Record<string, any> | { id: this.getKey() } | Claims embedded in the JWT. |
Returns a signed JWT string (via jwtSign from jcc-express-mvc).
Using the token with apiAuth
Protect API routes with the apiAuth middleware. Clients send the token as:
On each request, apiAuth:
- Verifies the JWT signature and access-token rules.
- Strips
exp,iat, andtypfrom the payload. - Looks up the user with the remaining claims as a
wherefilter (JCC Eloquent:User.where({ ...data }).first(); Sequelize/Mongoose use theirfindOneequivalents). - Attaches the resolved model to
req.user.
Example route pair:
Payload design tips
- Include fields that uniquely identify the user in your database (
id,email, etc.) —apiAuthuses them to reload the record. - Include
typ: "access"when you enableJWT_REQUIRE_TYPED_TOKENS=truein production (see JWT). - Do not put secrets (passwords, refresh tokens) in the JWT payload.
- Token lifetime is fixed at 22 hours in the current
createTokenimplementation.
Errors
If hasToken is not enabled on the model:
The global error handler returns 500 with the error message for ModelTokenError.
For cookie-based login (Auth.attempt), refresh tokens, and web auth middleware, see Authentication.
Summary
- Define models by extending
Model. - Prefer generated model files (
make:model) inapp/Models. - Configure table/key/mass-assignment/hidden/accessors directly in the class.
- Use
declare col?: Typefor every table column — gives full TypeScript type safety with zero runtime cost. - Add relationship methods to express domain links cleanly.
- Register lifecycle hooks in
static boot()withthis.creating(...),this.saved(...), etc.