I am getting into Loopback 4, coming from Sequelize and with a bit of experience with TypeORM.
My question is why in Loopback 4 the relations doesn't get constraints. I have seen https://loopback.io/doc/en/lb4/todo-list-tutorial-sqldb.html#specify-the-foreign-key-constraints-in-todo-model, but I don't get why it doesn't do it automatically, or if I'm doing something wrong.
Lets see an example with the same models for the three ORMS: a User
model that can have many Post
:
User --* Post
Sequelize:
User:
const User = sequelize.define(
'User',
{
id: {
type: Sequelize.INTEGER,
autoIncrement: true,
primaryKey: true,
allowNull: false,
},
name: {
type: Sequelize.STRING,
},
},
{}
);
User.associate = function(models) {
User.hasMany(models.Post, { onDelete: 'CASCADE' });
};
Post:
const Post = sequelize.define(
'Post',
{
id: {
type: Sequelize.INTEGER,
autoIncrement: true,
primaryKey: true,
allowNull: false,
},
title: {
type: Sequelize.STRING,
},
text: {
type: Sequelize.TEXT,
},
},
{}
);
Post.associate = function(models) {
Post.belongsTo(models.User, { onDelete: 'CASCADE' });
};
Result in MYSQL:
CREATE TABLE IF NOT EXISTS `test_sequelize`.`Users` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`name` VARCHAR(255) NULL DEFAULT NULL,
PRIMARY KEY (`id`))
ENGINE = InnoDB
AUTO_INCREMENT = 3
DEFAULT CHARACTER SET = utf8
COLLATE = utf8_unicode_ci;
CREATE TABLE IF NOT EXISTS `test_sequelize`.`Posts` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`title` VARCHAR(255) NULL DEFAULT NULL,
`text` TEXT NULL DEFAULT NULL,
`UserId` INT(11) NULL DEFAULT NULL,
PRIMARY KEY (`id`),
INDEX `UserId` (`UserId` ASC) ,
CONSTRAINT `posts_ibfk_1`
FOREIGN KEY (`UserId`)
REFERENCES `test_sequelize`.`Users` (`id`)
ON DELETE CASCADE
ON UPDATE CASCADE)
ENGINE = InnoDB
AUTO_INCREMENT = 4
DEFAULT CHARACTER SET = utf8
COLLATE = utf8_unicode_ci;
Ok, the constraints are there.
Now lets try with TypeORM:
TypeORM:
User:
import { Post } from './Post';
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: string;
@Column()
name: string;
@OneToMany(type => Post, post => post.user, { onDelete: 'CASCADE' })
posts: Post[];
}
Post:
import { User } from './User';
import { PostHasTag } from './PostHasTag';
import { Tag } from './Tag';
@Entity()
export class Post {
@PrimaryGeneratedColumn()
id: number;
@Column()
title: string;
@Column({ type: 'text' })
text: string;
@ManyToOne(type => User, user => user.posts)
user: User;
}
And this is the result in MySQL:
CREATE TABLE IF NOT EXISTS `test_typeorm`.`user` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`name` VARCHAR(255) NOT NULL,
PRIMARY KEY (`id`))
ENGINE = InnoDB
AUTO_INCREMENT = 3
DEFAULT CHARACTER SET = utf8
COLLATE = utf8_unicode_ci;
CREATE TABLE IF NOT EXISTS `test_typeorm`.`post` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`title` VARCHAR(255) NOT NULL,
`text` TEXT NOT NULL,
`userId` INT(11) NULL DEFAULT NULL,
PRIMARY KEY (`id`),
INDEX `FK_5c1cf55c308037b5aca1038a131` (`userId` ASC) ,
CONSTRAINT `FK_5c1cf55c308037b5aca1038a131`
FOREIGN KEY (`userId`)
REFERENCES `test_typeorm`.`user` (`id`)
ON DELETE NO ACTION
ON UPDATE NO ACTION)
ENGINE = InnoDB
AUTO_INCREMENT = 4
DEFAULT CHARACTER SET = utf8
COLLATE = utf8_unicode_ci;
Everything ok, constraints there. Now with Loopback 4.
Loopback 4:
The models, repositories, controllers and relations are generated by the cli, but I will only post the models:
User:
import {Post} from './post.model';
@model({settings: {}})
export class User extends Entity {
@property({
type: 'number',
id: true,
generated: true,
})
id?: number;
@property({
type: 'string',
required: true,
})
name: string;
@hasMany(() => Post)
posts: Post[];
constructor(data?: Partial<User>) {
super(data);
}
}
export interface UserRelations {
// describe navigational properties here
}
export type UserWithRelations = User & UserRelations;
Post:
import {Entity, model, property} from '@loopback/repository';
@model({settings: {}})
export class Post extends Entity {
@property({
type: 'number',
id: true,
generated: true,
})
id?: number;
@property({
type: 'string',
required: true,
})
title: string;
@property({
type: 'string',
})
text?: string;
@property({
type: 'number',
})
userId?: number;
constructor(data?: Partial<Post>) {
super(data);
}
}
export interface PostRelations {
// describe navigational properties here
}
export type PostWithRelations = Post & PostRelations;
And the MySQL:
CREATE TABLE IF NOT EXISTS `test_loopback`.`Post` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`title` VARCHAR(512) NOT NULL,
`text` VARCHAR(512) NULL DEFAULT NULL,
`userId` INT(11) NULL DEFAULT NULL,
PRIMARY KEY (`id`))
ENGINE = InnoDB
AUTO_INCREMENT = 2
DEFAULT CHARACTER SET = utf8
COLLATE = utf8_unicode_ci;
CREATE TABLE IF NOT EXISTS `test_loopback`.`User` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`name` VARCHAR(512) NOT NULL,
PRIMARY KEY (`id`))
ENGINE = InnoDB
AUTO_INCREMENT = 2
DEFAULT CHARACTER SET = utf8
COLLATE = utf8_unicode_ci;
And as you may see, there are no constraints there. Am I doing something wrong? Is this expected behaviour?