How do you set a custom default unique id string for the @PrimaryGeneratedColumn in a TypeORM Entity?
Asked Answered
T

3

12

I have a use case that calls for a custom string primary key in my tables. (I don't want to use the default 'uuid' provided by GraphQL, but instead want to use the shortid library to generate a custom unique id instead.)

I'm a TypeORM beginner, and I'm not finding anything about setting a custom default primary key in the docs. Is it possible to achieve what I want in the TypeORM PrimaryGeneratedColumn, or do I have to accomplish what I want by other means?

UPDATE: I learned I can use the @BeforeInsert listener to modify entities before saving them, but TypeORM still doesn't let me override the PrimaryGeneratedColumn('uuid') and use a shortId string because the shortId string is not a valid uuid.

Tipcat answered 4/9, 2018 at 1:48 Comment(6)
Depending on which database you are using, specifing @PrimaryGeneratedColumn('uuid') means that the database field type will be uuid, which you can't insert a non-valid UUID in. I'd suggest decorating with @PrimaryColumn('char', { length: <shortId length> }) and using a @BeforeInsert listener as you describe. This will give you a fixed-length character primary key for the shortId.Fahy
Looking again at the shortId docs, I noticed that the generated ID may be variable length; if so you'll want to use @PrimaryColumn('varchar', { length: <max-shortid-length> }) instead of the above...Fahy
@PrimaryColumn is exactly what I needed. I'm such a noob I didn't even know PrimaryColumn was a decorator :# my code ended up looking like this @PrimaryColumn('varchar', { default: shortid.generate(), length: 14 }) thanks for your help! I'd vote up your comments but it won't let me :/Tipcat
Posted as answer so you can up-vote it! BTW with the column default option, are you passing the function itself (ie default: shortid.generate) or calling it and defaulting to its return value (default: shortid.generate())? Unless I'm mistaken you'll need the former - otherwise TypeORM will have a single default value each time your app starts and will allow inserting one record but then error with a duplicate key violation for subsequent records.Fahy
@Fahy you are correct, I ran into duplicate key violation. However, when I tried the change you recommended, I kept running into a column "gz68lzqnmn" does not exist (the id changes each time) error. The only way around this I found was to keep default: shortid.generate() but then in the BeforeInsert hook I added this.id = shortid.generate(). Very hacky and sad :( but it works. If you want to look further into this you could pull this repo and yarn start to reproduce. No pressure though. Thanks again! github.com/podverse/podverse-api/tree/primaryDefaultBugTipcat
I'm pretty sure this is a quoting issue - the returned ID isn't quoted so is being interpreted as a column. Wrapping shortid.generate() and quoting the value should be all that is needed - e.g. default: () => `'${shortid.generate()}'` (note the single quotes around the ID).Fahy
F
14

The @PrimaryGeneratedColumn('uuid') decorator maps the column to a uuid database field type if the database supports it, which means the column values must be a valid UUIDs.

For your scenario I suggest decorating with: typescript @PrimaryColumn('varchar', { length: <max shortId length>, default: () => `'${shortid.generate()}'` })

Fahy answered 4/9, 2018 at 12:40 Comment(0)
N
7

@Timshel's answer does not work when using SQLite, because default values must be constant. It gives the following error:

UnhandledPromiseRejectionWarning: QueryFailedError: SQLITE_ERROR: default value of column [id] is not constant

Instead, you can use @BeforeInsert to achieve the desired result, like so:

@Field(() => ID)
@PrimaryColumn("varchar", {
  length: 20
})
id: string;

@BeforeInsert()
setId() {
  this.id = shortid.generate();
}
Neel answered 25/2, 2020 at 15:18 Comment(3)
@Timshel's answer also doesn't work for most other drivers. I tested it out using MySQL and when I created a migration from my entity the migration included: ALTER TABLE user CHANGE id id char(36) NOT NULL DEFAULT 177f7044-0805-4891-870e-8352cffa77bf. Looks like if you want TypeORM to do the value generation you have to use @BeforeInsertPatchwork
other problem arises as it's either not possible to update entities OR its possible to expose a way to create entities with arbitrary IDsVegetative
Please note that you have to pass an instance of the entity (and not a plain object) to the save() method.Sisile
B
2

Great thanks to the answers from Timshel and Oskari Liukku!

There is a complementary to their answer.

Recently, shortid was depricated, and on their main page they recommend to use nanoid.

nanoid on npm

Brinna answered 12/5, 2022 at 15:18 Comment(1)
But what if nanoid will create id with the same value? You wil get an error, no?Belew

© 2022 - 2024 — McMap. All rights reserved.