Search code examples
typescriptnestjstypeorm

TypeORM: Default values for primary column


I have a problem when trying to save an entity to the DB with a primary column with a default value using TypeORM (in NestJs).

TL;DR How on earth do you set default column values with TypeORM when default just doesn't seem to do anything?

EDIT 1: Okay, so default seems to be DB level, kind of missed that before, but still @BeforeInsert() should do the trick then (yet doesn't).

EDIT 2: So apparently @BeforeInsert() hook works but only if .save() is called with .save(new UserSession(userId)) and not with .save({ userId: userId }) (which of course requires the appropriate constructor in UserSession).

The setup

I have the entity UserSession, note the sessionId field, which is supposed to have a default value of whatever cryptoRandomString({ length: 128, type: 'alphanumeric' }) returns.

@Entity('user_session')
export class UserSession {
    
    @PrimaryColumn({ 
        name: 'session_id',
        default: () => cryptoRandomString({ length: 128, type: 'alphanumeric' })
    })
    sessionId: string
    
    @Column({ 
        name: 'user_id',
    })
    userId: string
   
}

I then use the TypeORM repository .save() function to write the object to the database like so:

@Injectable()
export class AuthService {
    constructor(
        @InjectRepository(UserSession)
        private readonly userSessionRepository: Repository<UserSession>
    ) {}

    async createNewSession(userId: string): Promise<void> {
        // Create new session
        const session = await this.userSessionRepository.save({
            userId: userId
        })

       // If only it got that far...
    }
}

The error

At this point I kind of expect TypeORM to set a default value for sessionId if nothing is set 'manually'. However, it just throws an error:

[Nest] 23088   - 04/06/2021, 23:18:44   [ExceptionsHandler] ER_NO_DEFAULT_FOR_FIELD: Field 'session_id' doesn't have a default value +4032ms
QueryFailedError: ER_NO_DEFAULT_FOR_FIELD: Field 'session_id' doesn't have a default value
    at new QueryFailedError (E:\SAC\projects\api-flexrent\node_modules\typeorm\error\QueryFailedError.js:12:28)
    at Query.<anonymous> (E:\SAC\projects\api-flexrent\node_modules\typeorm\driver\mysql\MysqlQueryRunner.js:217:45)
    at Query.<anonymous> (E:\SAC\projects\api-flexrent\node_modules\mysql\lib\Connection.js:526:10)
    at Query._callback (E:\SAC\projects\api-flexrent\node_modules\mysql\lib\Connection.js:488:16)
    at Query.Sequence.end (E:\SAC\projects\api-flexrent\node_modules\mysql\lib\protocol\sequences\Sequence.js:83:24)
    at Query.ErrorPacket (E:\SAC\projects\api-flexrent\node_modules\mysql\lib\protocol\sequences\Query.js:92:8)
    at Protocol._parsePacket (E:\SAC\projects\api-flexrent\node_modules\mysql\lib\protocol\Protocol.js:291:23)
    at Parser._parsePacket (E:\SAC\projects\api-flexrent\node_modules\mysql\lib\protocol\Parser.js:433:10)
    at Parser.write (E:\SAC\projects\api-flexrent\node_modules\mysql\lib\protocol\Parser.js:43:10)
    at Protocol.write (E:\SAC\projects\api-flexrent\node_modules\mysql\lib\protocol\Protocol.js:38:16)

Despair

After spending some time on good old google I tried several variations, which all did not work (and throw the same error):

@PrimaryColumn({ 
    name: 'session_id',
    default: cryptoRandomString({ length: 128, type: 'alphanumeric' })
})
sessionId: string

or

@PrimaryColumn({ 
    name: 'session_id',
    default: () => 'cryptoRandomString({ length: 128, type: "alphanumeric" })' // Why would that even work
})
sessionId: string

or

@PrimaryColumn({ 
    name: 'session_id',
    default: 'cryptoRandomString({ length: 128, type: "alphanumeric" })' // Why would that even work
})
sessionId: string

or

@PrimaryColumn({ 
    name: 'session_id',
    default: 'please work'
})
sessionId: string

or not setting default in @PrimaryColumn() but rather using @BeforeInsert(), which apparently isn't called when using .save()

@BeforeInsert()
beforeInsertAction(): void {
    this.sessionId = cryptoRandomString({ length: 128, type: 'alphanumeric' })
}

The only thing that did work was using a constructor for the UserSession class and setting the sessionId in there (which I thought kind of defeats the purpose of default values in TypeORM?):

constructor(
    userId: string
) {
    this.userId = userId
    this.sessionId = cryptoRandomString({ length: 128, type: 'alphanumeric' })
}

and calling it with

const session = await this.userSessionRepository.save(new UserSession(userId))

So, did I miss anything? Am I approaching the thing completely wrong, to begin with? I don't know anymore.


Solution

  • I eventually found a solution to the problem that works...

    I now defined my entity class like this:

    @Entity('user')
    export class User {
    
      @PrimaryColumn({ 
        name: 'user_id',
        type: 'varchar',
        length: 32
      })
      userId: string = cryptoRandomString({ length: 32, type: 'alphanumeric' })
    
      @Column({ 
        name: 'session_id',
        type: 'varchar',
        length: 128
      })
      sessionId: string = cryptoRandomString({ length: 128, type: 'alphanumeric' })
    
      @Column({ 
        name: 'email',
        type: 'varchar',
        length: 128
      })
      email: string
    
    }
    

    Now I can create my new user entity using repository's .create():

    const user: User = this.userRepo.create({ email: '[email protected]' })
    

    Which creates the following object:

    user: {
      userId: 'ivWW8oMGD8oMh2FL0vM3Grrh2FL0M3Di'
      sessionId: 'vM3DiW8oMGrh2FL0vM3DiW8oMGrh2FL0vM3DiW8oMGrh2FL0vM3DiW8oMGrh2FL0...'
      email: '[email protected]'
    }
    

    Which can then again be saved using the .save() method:

    await this.userRepo.save(user)