Skip to main content

Relations

Classo supports three types of relations between DataClasses — one-to-many, one-to-one, and many-to-many. Relations are defined directly on your DataClass using helper functions imported from the relations module.

Import

const { hasMany, hasOne, manyToMany, belongsTo, ON_DELETE } = require("@lol44lol/classo/databases/relations")

Relation Types

hasMany

Declares that the current DataClass has many records of another DataClass. No database column is created — this is metadata used for querying.

class UserDataClass extends DataClass {
username = createField(types.TEXT, false, false, [])

messages = hasMany(MessageDataClass) // user has many messages

getName() { return "users" }
}

hasOne

Declares that the current DataClass has exactly one record of another DataClass. No database column is created.

class UserDataClass extends DataClass {
username = createField(types.TEXT, false, false, [])

profile = hasOne(ProfileDataClass) // user has one profile

getName() { return "users" }
}

belongsTo

Declares that the current DataClass belongs to another DataClass. This creates an actual foreign key column in the database table.

class MessageDataClass extends DataClass {
text = createField(types.TEXT, false, false, [])

userID = belongsTo(UserDataClass, "_id", false, ON_DELETE.CASCADE, types.TEXT)

getName() { return "messages" }
}

belongsTo parameters:

ParameterTypeDescription
parentClassDataClassThe parent DataClass this belongs to
parentFieldStringThe field on the parent to reference (usually "_id")
uniqueBooleanSet true for one-to-one, false for one-to-many
onDeleteON_DELETEWhat happens when the parent is deleted
typetypes.xThe SQL type for the foreign key column

manyToMany

Declares a many-to-many relation. No column is created on either table — Classo automatically creates a join table when you call createTables.

class UserDataClass extends DataClass {
username = createField(types.TEXT, false, false, [])

rooms = manyToMany(RoomDataClass)

getName() { return "users" }
}

class RoomDataClass extends DataClass {
name = createField(types.TEXT, false, false, [])

users = manyToMany(UserDataClass)

getName() { return "rooms" }
}

Classo automatically creates a room_users join table with foreign keys pointing to both tables.


ON_DELETE Options

ON_DELETE controls what happens to child records when a parent record is deleted:

ConstantBehaviour
ON_DELETE.CASCADEDelete child records automatically
ON_DELETE.SET_NULLSet the foreign key to null
ON_DELETE.RESTRICTBlock the delete if children exist
ON_DELETE.NO_ACTIONSame as RESTRICT in most databases
ON_DELETE.SET_DEFAULTSet the foreign key to its default value

One-to-Many Example

A user has many messages. The foreign key lives on the messages table:

const { DataClass, DataClassFactory } = require("@lol44lol/classo/dataclasses/base")
const { createField, types } = require("@lol44lol/classo/databases/sqlite3")
const { hasMany, belongsTo, ON_DELETE } = require("@lol44lol/classo/databases/relations")

class UserDataClass extends DataClass {
username = createField(types.TEXT, false, false, [])

messages = hasMany(MessageDataClass)

getName() { return "users" }
}

class MessageDataClass extends DataClass {
text = createField(types.TEXT, false, false, [])

userID = belongsTo(UserDataClass, "_id", false, ON_DELETE.CASCADE, types.TEXT)

getName() { return "messages" }
}

One-to-One Example

A user has exactly one profile. Same as one-to-many but unique is set to true on belongsTo:

class UserDataClass extends DataClass {
username = createField(types.TEXT, false, false, [])

profile = hasOne(ProfileDataClass)

getName() { return "users" }
}

class ProfileDataClass extends DataClass {
bio = createField(types.TEXT, false, true, [])

userID = belongsTo(UserDataClass, "_id", true, ON_DELETE.CASCADE, types.TEXT) // unique: true

getName() { return "profiles" }
}

Many-to-Many Example

Users can join many rooms and rooms can have many users:

class UserDataClass extends DataClass {
username = createField(types.TEXT, false, false, [])

rooms = manyToMany(RoomDataClass)

getName() { return "users" }
}

class RoomDataClass extends DataClass {
name = createField(types.TEXT, false, false, [])

users = manyToMany(UserDataClass)

getName() { return "rooms" }
}

Classo creates a room_users join table automatically — you never have to define it yourself.

Adding a Many-to-Many Record

Use addRelation to link two records together in the join table:

// add user-1 to room-1
await db.addRelation(UserDataClass, RoomDataClass, 'user-1', 'room-1')

addRelation parameters:

ParameterTypeDescription
classOneDataClassFirst DataClass
classTwoDataClassSecond DataClass
idOneStringID of the first record
idTwoStringID of the second record

The order of classOne and classTwo doesn't matter — Classo figures out the join table name automatically.


Creating Tables with Relations

Always use createTables (not createTable) when your DataClasses have relations. Classo figures out the correct creation order automatically based on foreign key dependencies:

const db = new SQLiteDatabase("mydb.db")
await db.connect()

// Classo creates tables in the right order
// and creates join tables for many-to-many relations last
await db.createTables(UserDataClass, MessageDataClass, RoomDataClass)
Order matters

If you use createTable individually you risk foreign key errors because a parent table might not exist yet when the child table tries to reference it. Always use createTables when relations are involved.


MySQL Note

For MySQL, foreign key columns must use types.VARCHAR instead of types.TEXT:

// SQLite / PostgreSQL
userID = belongsTo(UserDataClass, "_id", false, ON_DELETE.CASCADE, types.TEXT)

// MySQL
userID = belongsTo(UserDataClass, "_id", false, ON_DELETE.CASCADE, types.VARCHAR)

What's Next

  • Query Builder — fetch related data using preload
  • Migration — update your schema as your DataClasses change