Marcus Junius Brutus
Marcus Junius Brutus

Reputation: 27286

how to integrate Kysely with NestJS pipe validation in a DRY way

I am implementing a NestJS backend with a PostgreSQL DB. I am no fan of ORMs so I am using Kyseley. Moreover I write my PostgreSQL DDL scripts by hand and then use kysely-codegen to generate the DTO objects once the database schema has been created. So my single source of truth is plain old DDL scripts written for a PostgreSQL database. So far so good.

I am now stuck at the following problem: I have no easy way to use the DTO objects (actually interfaces) automatically generated by kysely-codegen when doing pipe validation. Firstly, kyseley-codegen generates interfaces, not classes I can annotate with the annotation supplied in class-validator. Secondly, even if classes were created I wouldn't be able to modify them as this is automatically generated code and my source of truth is the PostgreSQL DDL scripts. I suppose that if kyseley-codegen generated classes I might have been able to use patch-package to add the class-validator annotation in a DRYish way but the point is moot since kysely-codegen generates interfaces.

It seems therefore that the only option available to me would be to bite the bullet and go fully WET and create DTO classes with class-validator annotations by hand. I would then use the DTO interfaces created by kysely-codegen in the actual DB queries and the hand crafted DTO classes in the NestJS validation pipelines. Of course being WET this entails maintaining the PostgreSQL DDL scripts and the DTO classes synchronized by hand.

Is there a practicable DRY approach that accomplishes the above or is it hopeless to try and have the PostgreSQL DDL scripts being the single source of truth?

Upvotes: 1

Views: 450

Answers (1)

Tyler Brown
Tyler Brown

Reputation: 157

I am using the same tooling here, and this is how I'm implementing my DTOs. I implement the interface that kysely-codegen provides, wrapped with kysely's Insertable interface to play nicely with the ColumnType type that the codegen library applies to more complex columns. Extending this interface ensures that, if anything changes with the source of truth Database interface from kysely-codegen, an error will occur downstream in my DTOs.


export class CreateExampleTableDto implements Partial<Insertable<ExampleTable>> { /***/ }

With regards to keeping everything DRY, I don't think that's an entirely feasible goal here. Per the Nest docs, DTOs are constructs that intentionally represent an object's structure at the point in time in which it comes over the wire. To me, it makes sense to me that kysely-codegen library only generates Interfaces and not actual JS Classes, as Classes generally represent real objects with a uniform shape of properties that actually exist within that object, versus an Interface only describing the shape of an object. Classes with class-validator properties on them that are also used for our Kysely Database interface would create a lot of false positives, as all of the class-validator metadata on certain rows would be 'wrong', per how Kysely represents it's own types for certain columns (e.g. ColumnType<T, U, V>).

In my answer above, when accepting data that intends to create a record within the database, often there will only be partial data from that entire class. Take for example a 'created at' or 'updated at' column, or maybe an internal ID. These are often things a client won't know about, and definitely shouldn't have the responsibility of creating or updating - the source of truth there should be your server. Maintaining Partial<T> DTOs, I believe, is an acceptable middle ground, and easier to customize or maintain if you have multiple incoming or outgoing parameters that rely on Nest Pipes.

Upvotes: 0

Related Questions