typeorm: Unclear documentation: create() doesn't actually insert new row

Issue type:

[ ] question [ ] bug report [ ] feature request [x] documentation issue

Database system/driver:

[ ] cordova [ ] mongodb [ ] mssql [ ] mysql / mariadb [ ] oracle [ ] postgres [x] sqlite [ ] sqljs [ ] websql

TypeORM version:

[x] latest [ ] @next [ ] 0.x.x (or put your version here)

Steps to reproduce or a small repository showing the problem:

Currently, repository.create() creates new instance of entity, but doesn’t put it into db - you have to manually call repository.save() afterwards so that typeorm would actually run INSERT statement. I think that a lot of developers may not understand it from current documentation, and that it should be said so explicitly to avoid confusion.

(Will submit PR to docs myself if maintainers approve this).

About this issue

  • Original URL
  • State: open
  • Created 6 years ago
  • Reactions: 79
  • Comments: 58 (11 by maintainers)

Most upvoted comments

@pleerock that’s exactly the place I was talking about. You’re absolutely right that it doesn’t say that it inserts an element into a database, and I did feel like an idiot after I realized my mistake.

However, I don’t think that I’m the only one, and adding a short disclaimer would prevent a lot of other developers from making a similar mistake as me.

I had the same issue, it wasn’t until I read this post that I realised what a dork I am. 😃

I think this problem exist only for people who used sequelize, since create creates and saves in there. Can you please tell me where exactly “create” was not explained properly in docs?

I know we have this line here.

create - Creates a new instance of User. Optionally accepts an object literal with user properties which will be written into newly created user object

And this explanation clearly says that create creates a new instance of User object and does not tell anything about saving into the database. Maybe you mean another place?

Maybe we shall just remove this create method 🤔

Maybe a createAndSave method will be great here.

I just spent 20mins scratching my head wondering why create is not inserting any records to the DB lol

I find the create method useful, it allows me to easily create multiple instances and skip having to do assignments e.g.

const values = { name: 'Bloop', email: 'bloop@bleep.com' }
const myEntity = new MyEntity();
Object.assign(myEntity, values);
myEntityRepo.save(myEntity);

can simply be written as:

const myEntity = myEntityRepo
    .create({ name: 'Bloop', email: 'bloop@bleep.com' })
    .save();

You can also use arrays to create multiple entity instances:

const myEntities = MyEntity.create([
    { name: 'Bloop', email: 'bloop@bleep.com' },
    { name: 'Bleep', email: 'bleep@bloop.com' }
]);
myEntityRepo.save(myEntities);

I personally find the create method to be a useful shortcut and don’t see where the confusion persists, a quick look at the docs and you can easily work out whats going on.

This is very confusing

It’s indeed really confusing. If it is simply an utility function for creating an object instance without interacting with the database, I really don’t see any point that it should exists in TypeORM (especially in the Repository and EntityManager). 😹

😈 yeah create without Promise will hit DB.

Maybe just rename to createInstance or just add big note to docs. We have merge, hasId, getId. It will be shame do not have create-like method.

@golergka Same here, was really confused by the save vs insert for a moment…

I think the create method should be marked as deprecated and removed in a later release. Same with merge.

It’s confusing & provides little value. What other ORM has this?

The examples of its use in this thread can be handled by creating a base entity for your project that has a one line function. For objects, there’s already a mechanism to create a new instance - a constructor.

It seems to mostly have existed as a helper for the internals to create instances & combine instances. Mostly internal subject executor and relationship loader code.

If you’d like to implement it yourself for your entities you can absolutely do that - but on the repository it doesn’t make much sense when you can just as easily set up a constructor or write your own static “create” method that is less prone to errors than this one will be.

@Autognome are you sure it requires all the properties? Pretty sure it doesn’t & you can call save with partials. If you can’t that’s a separate bug.

This works:

import {Column, Entity, PrimaryGeneratedColumn} from "../../../../src";

@Entity()
export class Address {
    @PrimaryGeneratedColumn()
    id: number;
    @Column()
    city: string;
    @Column({ nullable: true })
    street: string;
}

const addressRepository = connection.getRepository(Address);
const paris = await addressRepository.save({ city: 'Paris' });

@vlapo it has almost zero value. What is the point to do getRepository(User).create() when you can just do new User() ? I have same thoughts about merge, we can use object rest to do same and its much more nicer

also, .create matches the Create of CRUD, which to me aligns it with “hey, something’s going to happen here”.

This needs to be changed immediately. This goes against all conventions and is extraordinarily confusing.

And still prisma is wrong. Having more downloads doesn’t mean that they are right.

They are the ones that need to clarify their documentation. And you should definitely comment on the issue that someone certainly opened there to defend their choice instead of asking TypeORM to explain why they did the correct semantic choice.

In Prisma create is used to insert a record into a database: https://www.prisma.io/docs/concepts/components/prisma-client/crud. The “Create” in CRUD is analogous to an insert (https://en.wikipedia.org/wiki/Create,_read,_update_and_delete). I’ll agree that in OOP create is often analogous to new, but when talking about DBs create is strongly associated with an insert.

My suggestion: remove it entirely. A repository is supposed to persist and fetch entity of the domain. The domain is the only context able to create an entity.

That typeorm exposes a way to create an entity is a violation to begin with, in my opinion.

Any suggestions other than createInstance ? I would prefer a single, simple and straightforward word.

@cour64 Instead of create then save, have you tried simply save? Like:

  const myEntity = myEntityRepo.save({ name: 'x', email: 'y' });

  const myEntities = myEntityRepo.save([
    { name: 'Bloop', email: 'bloop@bleep.com' },
    { name: 'Bleep', email: 'bleep@bloop.com' },
  ]);

The term create is analogous to the structural token new.

Considering a type Foo, const foo = new Foo(); and const foo = createFoo(); are analogous: in both case an instance of Foo is created and assigned to the constant foo.

I’m convinced that nobody would ever expect that new Foo() actually insert a record into a database. There is no reason why createFoo would behave differently as both are exactly the same thing: both createFoo and new Foo are factories to create an instance of Foo.

I think that this thread needs to stop being derailed by persons that try to convince us that the term create means anything else than “create an instance of”. It was never the case. And never will.

Very much agree with @pyyding. It’s not obvious to me from the documentation that it doesn’t save at the same time. “Create” is an ambiguous term. For example the manpages of touch say:

“The touch utility sets the modification and access times of files. If any file does not exist, it is created with default permissions.”

and in MySQL “CREATE” creates and saves the new table, it doesn’t just create a table that then needs to be committed/saved.

Regarding the original request I do agree that one shouldn’t create a breaking change, but I think a documentation would be very helpful

@pleerock .instantiate(?

@golergka The semantics of this library are a bit off. I rectify this on my end by creating a class called Model that extends BaseEntity, and have all my models extend Model instead of BaseEntity i.e

import {
    BaseEntity,
    ObjectType,
    ObjectID,
    FindOneOptions,
    FindConditions,
    DeepPartial,
} from 'typeorm';

export class Model extends BaseEntity {

    constructor(fields?: object) {
        super();
        if (fields) this.setFields(fields);
    }

    setFields(fields: any) {
        const entries = Object.entries(fields);
        for (const [k, v] of entries) {
            this[k] = v;
        }
    }

    /**
     * Creates a new entity instance.
     */
    static create<T extends Model>(this: ObjectType<T>): T;

    /**
     * Creates a new entities and copies all entity properties from given objects into their new entities.
     * Note that it copies only properties that present in entity schema.
     */
    static create<T extends Model>(this: ObjectType<T>, entityLikeArray: DeepPartial<T>[]): T;

    /**
     * Creates a new entity instance and copies all entity properties from this object into a new entity.
     * Note that it copies only properties that present in entity schema.
     */
    static create<T extends Model>(this: ObjectType<T>, entityLike: DeepPartial<T>): T;

    /**
     * Creates a new entity instance and copies all entity properties from this object into a new entity.
     * Note that it copies only properties that present in entity schema.
     */
    static create<T extends Model>(this: ObjectType<T>, entityOrEntities?: DeepPartial<T> | DeepPartial<T>[]): Promise<T> {
        const newT = (this as any).getRepository().create(entityOrEntities);
        return newT.save();
    }

}

In short this allows:

  • ModelChild.create to create row[s] in the DB and also return the created item[s].
  • expected const modelChildInstance = new ModelChild({...data}); behavior.

Prisma, which gets more weekly downloads than typeorm uses create to insert a row. You can argue that the semantics of create shouldn’t be to insert a row, but arguing no one should get confused about whether or not create will insert a row (which is effectively what you’re doing) is pretty out of touch. I’ll leave my comments there, because I don’t think this is turning into a very productive discussion.

Yes, it would be fundamentally wrong if a library would save a row into a database in response to the creation of a row instance. As I said, nobody would ever argue that such a behaviour is legit with new Foo.

I can’t comment about having a line about this in the documentation but what I can say for sure is that DBAL libraries that insert a row in response to a create call are the ones that should add a disclaimer in their documentation because it is a non expected behaviour.

TypeORM’s semantic and behaviour are correct here. It is not because other libraries do differently that TypeORM should adapt, especially when the behaviour of those other libraries is highly debatable to begin with.

The term create is analogous to the structural token new.

Considering a type Foo, const foo = new Foo(); and const foo = createFoo(); are analogous: in both case an instance of Foo is created and assigned to the constant foo.

I’m convinced that nobody would ever expect that new Foo() actually insert a record into a database. There is no reason why createFoo would behave differently as both are exactly the same thing: both createFoo and new Foo are factories to create an instance of Foo.

I think that this thread needs to stop being derailed by persons that try to convince us that the term create means anything else than “create an instance of”. It was never the case. And never will.

I fundamentally disagree with this argument.

@vlapo it has almost zero value. What is the point to do getRepository(User).create() when you can just do new User() ? I have same thoughts about merge, we can use object rest to do same and its much more nicer

The purpose is populating entities from objects as described. It is quite useful if you’re accepting JSON and wish to create an entity without saving in the database, mostly because it’s already saved and the request came from a previous call.

Eg think of user authentication where you use jwt tokens to store user data in the token and you don’t wish to query the database every time a user makes a request but you wish to use the already defined entity to work with the user entity. Hence you can reconstruct the entity from an object and use it as if you called find one. Straight forward use case.

Yup, same issue as everyone else here. I just spend 4 hours trying to debug why my test setup was not working until i figured out using a debugger that create does not in fact insert into the DB. This goes against basically every convention of what “create” represents. So no offense you guys - a beautiful ORM all in all, but this is a bad design decision IMHO.

@cour64 Instead of create then save, have you tried simply save? Like:

  const myEntity = myEntityRepo.save({ name: 'x', email: 'y' });

  const myEntities = myEntityRepo.save([
    { name: 'Bloop', email: 'bloop@bleep.com' },
    { name: 'Bleep', email: 'bleep@bloop.com' },
  ]);

What if you want to create nested instances and then call save in the main one to do a cascade save in one operation? I think it is useful for creating instances with object’s syntax but it can be confusing so maybe renaming it to createInstance should be fine.

I also was confused by this. Coming from activerecord, I expected that create was a combination of new and save.

In my use case create allows me to create an instance with a DTO and then call save on that instance, skipping assignments.

Using only save above doesn’t work in this case in my experience because it expects all properties of the entity.

NOTE: I am a noob developer


interface CreateUserDto {
    firstName: string,
    lastName: string,
    email: string,
    password: string,
  }
  
class UserModel {
    id: number;
    firstName: string;
    lastName: string;
    email: string;
    password: string;
    lastLogin: Date;
    createdAt: Date;
}

class UserService {
    async createUser(createUserDTO: CreateUserDto): Promise<UserModel> {

       return await UserModel.create(createUserDTO).save();

    }
}