Guides

Database

Define database tables, apply modifiers for hashing and encryption, and perform CRUD operations with data models in AntelopeJS.

Prerequisites

Database operations require the @antelopejs/interface-database-decorators package. Install it in your module and import from @antelopejs/interface-database-decorators.

npm install @antelopejs/interface-database-decorators

All table classes, decorators, modifiers, and model functions referenced in this guide come from this package. No additional dependencies are needed.

Defining Tables

You define tables as classes that extend the Table base class. The @RegisterTable decorator registers the class with the database system under a specific table name and schema. Each property on the class represents a column.

src/db/tables/user.ts
import { Table, RegisterTable, Index } from "@antelopejs/interface-database-decorators";

@RegisterTable("users", "main")
class User extends Table {
  @Index()
  declare email: string;

  declare name: string;
  declare age: number;
}

export { User };

The first argument to @RegisterTable is the table name and the second is the schema name. Default values on properties define the column types and provide fallback values for new records. The @Index decorator marks a property as indexed for faster lookups.

Table is a base class, not a decorator. Always extend it with class MyTable extends Table.

Indexing

The @Index decorator marks a property for database indexing. Indexes speed up queries that filter or sort by the indexed column. You can group multiple columns into a composite index by specifying a group name.

src/db/tables/product.ts
import { Table, RegisterTable, Index } from "@antelopejs/interface-database-decorators";

@RegisterTable("products", "main")
class Product extends Table {
  @Index()
  declare sku: string;

  @Index({ group: "category_brand" })
  declare category: string;

  @Index({ group: "category_brand" })
  declare brand: string;

  declare name: string;
  declare price: number;
}

export { Product };

Properties with the same group value form a composite index. Queries that filter by both category and brand benefit from the composite index.

Table Modifiers

Modifiers add specialized behavior to table properties. Apply modifiers through Table.with(), which returns an extended base class with the modifier capabilities enabled. You can chain multiple modifiers together.

Hashing

The HashModifier enables the @Hashed decorator for properties that store hashed values, such as passwords. Hashed values are one-way transformed before storage and cannot be reversed.

src/db/tables/user.ts
import { Table, RegisterTable, Index, Hashed } from "@antelopejs/interface-database-decorators";
import { HashModifier } from "@antelopejs/interface-database-decorators";

@RegisterTable("users", "main")
class User extends Table.with(HashModifier) {
  @Index()
  declare email: string;

  @Hashed()
  declare password: string;

  declare name: string;
}

export { User };

When a record is inserted or updated, the password field is automatically hashed before it reaches the database. The original plaintext value is never stored.

Encryption

The EncryptionModifier enables the @Encrypted decorator for properties that must be stored encrypted. Unlike hashing, encrypted values can be decrypted when read back.

src/db/tables/user.ts
import { Table, RegisterTable, Index, Encrypted } from "@antelopejs/interface-database-decorators";
import { EncryptionModifier } from "@antelopejs/interface-database-decorators";

@RegisterTable("users", "main")
class User extends Table.with(EncryptionModifier) {
  @Index()
  declare email: string;

  @Encrypted({ secretKey: "your-32-char-secret-key-here!!" })
  declare socialSecurityNumber: string;

  declare name: string;
}

export { User };

Encrypted fields are transparently encrypted on write and decrypted on read. The secretKey option is required and specifies the encryption key. You can also configure the algorithm (defaults to aes-256-gcm) and ivSize (defaults to 16).

Combining Modifiers

You can apply multiple modifiers at once by passing them all to Table.with(). This combination enables both @Hashed and @Encrypted decorators on the same table class.

src/db/tables/user.ts
import { Table, RegisterTable, Index, Hashed, Encrypted } from "@antelopejs/interface-database-decorators";
import { HashModifier, EncryptionModifier } from "@antelopejs/interface-database-decorators";

@RegisterTable("users", "main")
class User extends Table.with(HashModifier, EncryptionModifier) {
  @Index()
  declare email: string;

  @Hashed()
  declare password: string;

  @Encrypted({ secretKey: "your-32-char-secret-key-here!!" })
  declare socialSecurityNumber: string;

  declare name: string;
}

export { User };

Creating a Database Schema Instance

Before your models can interact with the database, you must create a schema instance. The CreateDatabaseSchemaInstance function initializes the schema that your tables are registered under.

src/db/schema.ts
import { CreateDatabaseSchemaInstance } from "@antelopejs/interface-database-decorators";

await CreateDatabaseSchemaInstance("main");

The schema name passed here must match the schema name used in your @RegisterTable decorators. Call this function once during module initialization (for example, in the module's construct function). The function is async, so make sure to await it.

Data Models

The BasicDataModel function generates a model class with built-in CRUD operations for a registered table. It accepts the table class and returns a new class that you can extend with custom methods.

src/db/models/user-model.ts
import { BasicDataModel, GetModel } from "@antelopejs/interface-database-decorators";
import { User } from "../tables/user";

class UserModel extends BasicDataModel(User) {
  async getUserByEmail(email: string) {
    return this.table.filter({ email }).run();
  }
}

export function getUserModel() {
  return GetModel(UserModel);
}

export { UserModel };
BasicDataModel is a function that returns a class, not a decorator. Use extends BasicDataModel(TableClass) to create your model. Model instances require a database connection, so use GetModel(YourModel) to obtain a properly initialized instance rather than calling new directly.

The returned model includes these built-in instance methods:

MethodDescription
get(id)Retrieve a single record by its primary key
getBy(index, ...keys)Retrieve records matching a given index
getAll()Retrieve all records
insert(data)Create a new record
update(id, data)Update an existing record by ID
delete(id)Remove a record by ID

The model also provides static methods for data conversion:

Static MethodDescription
fromPlainData()Create a model instance from plain data
fromDatabase()Create a model instance from database format
toDatabase()Convert a model instance to database format

CRUD Operations

Once you have a model instance, all standard CRUD operations are available as async methods. Each method handles serialization, modifier processing (hashing, encryption), and database communication automatically.

import { getUserModel } from "./models/user-model";

const userModel = getUserModel();

// Create
await userModel.insert({
  name: "Alice",
  email: "[email protected]",
  password: "secret",
});

// Read all
const allUsers = await userModel.getAll();

// Read by index
const users = await userModel.getBy("email", "[email protected]");
const user = users[0];

// Read by primary key
const found = await userModel.get(user._id);

// Update
await userModel.update(user._id, { name: "Alice Updated" });

// Delete
await userModel.delete(user._id);

When inserting a record with a @Hashed property, the value is hashed before storage. When reading a record with an @Encrypted property, the value is decrypted automatically.

Fixtures

The @Fixture decorator provides seed data for development and testing. It takes a generator function that returns an array of records to insert when the table is first created.

src/db/tables/role.ts
import { Table, RegisterTable, Fixture } from "@antelopejs/interface-database-decorators";

@Fixture(() => [
  { name: "admin", permissions: ["read", "write", "delete"] },
  { name: "editor", permissions: ["read", "write"] },
  { name: "viewer", permissions: ["read"] },
])
@RegisterTable("roles", "main")
class Role extends Table {
  declare name: string;
  permissions: string[] = [];
}

export { Role };

Fixtures run once during schema initialization. They are useful for populating lookup tables, default roles, or any data that must exist before the application starts serving requests.