Skip to main content

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.
  • float
    • Floating-point column. Options: unsigned: boolean, precision, scale.
  • boolean
    • Boolean column.
  • string
    • VARCHAR column. Options: size (default 255).
  • 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).
  • json
    • JSON column. Values are serialized/deserialized automatically.
  • reference
    • Stored as string or enum depending on models. Options: id_field (default id), models: string[].

Common field properties​

  • unique: boolean
    • Unique index on the column.
  • required: boolean
    • When true, column is NOT NULL; when omitted/false, column allows NULL.
  • 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​

  1. 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.

  1. 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.

  1. 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_tags with post_id, tag_id PK.
  • 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() }

Validation​

NormalJS enforces validation at two levels:

  • Database constraints, driven by field options
    • required: adds NOT NULL on the column (throws on NULL during create/update)
    • unique: creates a UNIQUE index on the column (enforced by the DB)
  • Application-level validators
    • validate: rules evaluated in application code before insert/update

Validation runs automatically during Model.create() and record.flush() for all stored fields.

Required (NOT NULL)​

email: { type: 'string', required: true }
  • Adds a NOT NULL constraint to the column.
  • When the value is missing/null, an error is thrown during create/update.

Unique (database unique index)​

email: { type: 'string', unique: true }
  • Creates a UNIQUE index at schema sync.
  • The database will reject duplicates; you’ll get a constraint error on insert/update if violated.

String validators (validate option)​

For string fields, you can attach validators via the validate option. These are checked in app code using the bundled validator library.

Supported rules include:

  • isEmail
  • isIP4 / isIP6
  • isURL
  • isDataURI
  • isSemVer
  • isHexColor
  • isFQDN
  • is: regex pattern must match
  • not: regex pattern must NOT match

Examples:

class Users {
static _name = 'Users';
static table = 'users';
static fields = {
id: 'primary',
// Required + unique + email validator
email: {
type: 'string',
required: true,
unique: true,
validate: { isEmail: true },
},

// Regex must match (starts with A followed by 2 digits)
code: { type: 'string', validate: { is: /^A\d{2}$/ } },

// Regex must NOT match (simple content filter)
bio: { type: 'string', validate: { not: /forbidden|banned/i } },

// IPv4 only
ip4: { type: 'string', validate: { isIP4: true } },
};
}

Notes:

  • validate is evaluated in application code at create/update time; it does not add a database constraint.
  • required and unique translate to real database constraints via schema sync.
  • You can combine required, unique, and validate on the same field.