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
).
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...
}
}
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)
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.
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)