Skip to main content

Relations

Assume we have models user,user detail and program :

import { EzBackend, EzModel, Type } from "@ezbackend/common";

const app = new EzBackend()

const user = new EzModel('User', {
name: Type.VARCHAR
})

const userDetail = new EzModel('UserDetail', {
age: Type.INT
})

const program = new EzModel('Program', {
name: Type.VARCHAR
})

app.addApp('user',user,{prefix:'user'})
app.addApp('userDetail',userDetail,{prefix:'user-detail'})
app.addApp('program',program,{prefix:'program'})

app.start()

One to One relations

To create a one-to-one relation between User and the User Details we can add the following to our User model:

User Model
const user = new EzModel('User', {
name: Type.VARCHAR,
detail: {
type: Type.ONE_TO_ONE,
target: 'UserDetail',
joinColumn: true
}
})

Each of the added properties performs a different functionality:

target - Specifies that a User has one User Details and vice versa

joinColumn - Specifies that the User model contains the User Details foreign key

A more detailed explanation can be found here

One to Many relations

To create a one-to-many relation between program and users we can add the following to our models:

User and Program Models
const user = new EzModel('User', {
.
.
program: { //Property Name = 'program'
type: Type.MANY_TO_ONE,
target:'Program',
inverseSide:'users' //NOTICE PLURALITY
}
})

const program = new EzModel('Program', {
.
.
users: { //NOTICE PLURALITY
type: Type.ONE_TO_MANY,
target:'User',
inverseSide:'program' //Thus on the inverse side I am 'program'
}
})

Each of the added properties performs a different functionality:

inverseSide - The name of the property on the other relation.

For one-to-many relations, you must define both

Type.ONE_TO_MANY - Put this on the model where that is the 'one'

Type.MANY_TO_ONE - Put this on the model where that is the 'many'

A more detailed explanation can be found here

Many to Many relations

To create a many-to-many relation between programs and users we can add the following to our models:

User and Program Models
const user = new EzModel('User', {
.
.
program: {
type: Type.MANY_TO_MANY,
target:'Program',
joinTable:true
}
})

const program = new EzModel('Program', {
.
.
//No need anything here
})

For Many to Many relations you need the following:

Type.ManyToMany - Specifies a Program has many Users and vice versa

joinTable - Specifies that the foreign key to the join table is on this side of the relation

A more detailed explanation can be found here

Nested Creation

For nested creation to work, we need to enable cascade creation. We can do so by adding cascade:true

User Model
const user = new EzModel('User', {
name: Type.VARCHAR,
detail: {
type: Type.ONE_TO_ONE,
target: 'UserDetail',
joinColumn: true,
cascade:true
}
})

We can test all our endpoints at the generated docs

We can

  1. Click on the POST request for the url /User/
  2. Click on Try it out
  3. Use the following request body:
{ "name": "Robert", "detail": { "age": 70 } }

Now we can get all the Users using the GET request for /User/ to obtain:

[
{
"id": 1,
"name": "Robert"
}
]

And all the user details with the GET request for /UserDetail/

[
{
"id": 1,
"age": 70
}
]

Nested Read

Right now we notice that the returned user does not come with his user details. However, if we want to return the nested field we can add eager:true

User Model
const user = new EzModel('User', {
name: Type.VARCHAR,
detail: {
type: Type.ONE_TO_ONE,
target: 'UserDetail',
joinColumn: true,
eager:true
}
})

Now, when we get the parents we have:

[
{
"id": 1,
"name": "Robert",
"detail": {
"id": 1,
"age": 70
}
}
]

Updating by ID

Let's say we create the user details seperately with

{"age":24}

And a user,

{"name":"Rebecca"}

To update by the user's account by ID, we have to make a reference to the foreign key that typeorm automatically generates.

We can do this by adding the column detailId

User Model
const user = new EzModel('User', {
name: Type.VARCHAR,
detail: {
type: Type.ONE_TO_ONE,
target: 'UserDetail',
joinColumn: true,
},
detailId: {
type: Type.INT,
nullable:true
}
})

Now we can update the user with her detail's id by using the PATCH request using the user's id

{"detailId" : 1}

Now if we obtain all the parents, we find that the user now has her details

[
{
"id": 1,
"name": "Rebecca",
"baby": {
"id": 1,
"age": 24
},
"babyId": 1
}
]

Nested Delete

When we remove a user, by default, the database throws an error if their details still exist. If we want to make deleting the User deletes the User Details, we can use

User Model
const user = new EzModel('User', {
name: Type.VARCHAR,
detail: {
type: Type.ONE_TO_ONE,
target: 'UserDetail',
joinColumn: true,
onDelete: 'CASCADE'
}
})

Nested Functionality

All routes support nested functionality with relations. You can configure the nested functionality in the options of your relation

User and Program Example
const user = new EzModel('User', {...})

const program = new EzModel('Program', {
name: Type.VARCHAR,
users: {
type : Type.ONE_TO_MANY,
.
.
eager: (true | false),
cascade: (true | false | ['insert','update','remove']),
onDelete: ("RESTRICT" | "CASCADE" | "SET NULL" | "DEFAULT" | "NO ACTION"),
onUpdate: ("RESTRICT" | "CASCADE" | "SET NULL" | "DEFAULT")
}
})
OptionDescriptiondefault value
eagerLoads the nested data on GET if truefalse
cascadeUpdates the nested data on PATCH if truefalse
onDelete RESTRICTRestricts Delete if nested data existsRESTRICT
onDelete CASCADEDeletes Nested data if nested data existsRESTRICT

Understanding how it works

Oversimplifying, the apis are mapped to the typeorm functions in the following way

nameurlendpointaction
create/POSTsave
readMany/GETfind
read/:idGETfindOne
update/:idPATCHupdate
delete/:idDELETEupdate

So in order to adjust the functionality of the endpoints, you just need to adjust the functionality of your generated schemas