Fields
NormalJS ships a concise field type system: primary, string, number, boolean, date/datetime, enum, text, and relations.
Models declare fields via a static fields object on the class. Each entry describes the column type, constraints, defaults, and (optionally) relations.
Quick example:
class Users {
static name = 'Users';
static table = 'users';
static fields = {
id: 'primary', // auto-increment PK
firstname: 'string',
lastname: { type: 'string', required: true },
email: { type: 'string', unique: true, required: true },
password_hash: { type: 'string', size: 64, required: true },
active: { type: 'boolean', default: true },
status: { type: 'enum', values: ['user', 'admin', 'moderator'], default: 'user' },
created_at: { type: 'datetime', default: () => new Date() },
updated_at: { type: 'datetime', default: () => new Date() },
};
get name() {
return `${this.firstname} ${this.lastname}`;
}
}
Supported field types
Primitive and special types supported by the schema builder and runtime:
- primary
- Auto-increment integer primary key.
- integer (alias: number)
- Integer column. Options:
unsigned: boolean.
- Integer column. Options:
- float
- Floating-point column. Options:
unsigned: boolean,precision,scale.
- Floating-point column. Options:
- boolean
- Boolean column.
- string
- VARCHAR column. Options:
size(default 255).
- VARCHAR column. Options:
- text
- TEXT column for large strings.
- date
- Date (no time) column. Values are Date objects or ISO strings; serialized as ISO.
- datetime (alias: timestamp)
- Timestamp column (no timezone). Values are Date objects; serialized as epoch millis.
- enum
- Database enum column. Options:
values: string[](required).
- Database enum column. Options:
- json
- JSON column. Values are serialized/deserialized automatically.
- reference
- Stored as string or enum depending on
models. Options:id_field(defaultid),models: string[].
- Stored as string or enum depending on
Common field properties
- unique: boolean
- Unique index on the column.
- required: boolean
- When
true, column is NOT NULL; when omitted/false, column allows NULL.
- When
- default: value | () => value
- If a function, it’s applied at insert time in app code (not a DB default).
- index: boolean
- Adds an index on the column (where supported for the type).
Per-type extras:
- string:
size - integer/float:
unsigned, and for float:precision,scale - enum:
values - reference:
id_field,models
Relations
- Many-to-one (scalar FK on this table)
// Post belongs to a User; cascade controls ON DELETE behavior
author: { type: "many-to-one", model: "Users", cascade: true }
This creates an integer author column referencing users.id. When cascade: true, foreign deletes cascade; when false, they set NULL.
- One-to-many (parent has a collection of child rows)
// User has many Posts via Posts.author
posts: { type: "one-to-many", foreign: "Posts.author" }
Use the pattern ChildModel.fkFieldName, where fkFieldName is the ManyToOne field name on the child model.
- Many-to-many (join table)
// Posts side
tags: { type: "many-to-many", model: "Tags" }
// Tags side
posts: { type: "many-to-many", model: "Posts" }
- A join table is created automatically as
rel_<left>_<right>(lexicographic by table name), e.g.rel_posts_tagswithpost_id,tag_idPK. - You can force a specific name via
joinTable: "my_join". - Instances expose a collection wrapper:
await post.tags.load()await post.tags.add(tagOrId)await post.tags.remove(tagOrId)await post.tags.clear()
Defaults
- Static values (DB default where supported by the column type):
active: { type: "boolean", default: true } - Function values (applied in app code at insert time):
created_at: { type: "datetime", default: () => new Date() }