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