| That is a non-goal. One of the biggest advantages of Deepkit and its runtime type feature is that you can reuse TypeScript types throughout your whole application stack. This does not work with Fastify and Prisma. Let me give you an example. Let's say you build a little user management app and need a `User` type. So you define one: interface User {
id: number;
username: string;
created: Date;
email: string;
}
If you want to build a REST API that returns that user, you can just do that: router.get('/user/:id', (id: number & Positive): User => {
//...
});
Deepkit automatically serialises the object into JSON using the type information in `User`.You want to accept a User object for creating a user in the database? No problem: router.post('/user', (user: User): User => {
//...
});
You probably want to enrich the interface with additional constraints: interface User {
id: integer & Positive;
username: string & MinLength<3> & MaxLength<32>;
created: Date;
email: Email;
}
And with that the framework automatically validates all incoming requests against the User object and its containing validation constraints. It also deserialises it automatically from JSON for example. If the `User` is a class, it instantiates a class instance.But you don't want to require all fields, like id and created should be set by the server: router.post('/user', (user: Omit<User, 'id' |'created'>): User => {
//...
});
This works equally well. And the best part: you get API documentation for free with the Deepkit API Console, just like you know from Swagger UI.Ok, but we need to save the User in our database, right? We do not need to duplicate the User in yet another language like with Prisma. Just re-use the User interface and annotate the fields. interface User {
id: integer & PrimaryKey & AutoIncrement;
username: string & MinLength<3> & MaxLength<32> & Unique;
created: Date;
email: Email & Unique;
}
We still use the very same interface, but have added additional meta-data so you can use that exact same type now also for the ORM. router.get('/user/:id', async (id: number & Positive, db: Database): Promise<User> => {
return await db.query<User>().filter({id}).findOne();
});
Next, let's go to the frontend. We obviously have to request the data from our rest API. We can just do that and reuse the `User` interface once again. const userJson = await fetch('/user/2');
const user = cast<User>(userJson);
`cast` automatically validates and deserialises the received JSON. We import `User` wherever needed and this works because the meta-data is not tightly coupled to any library, so you won't pull in for example ORM code just because you use database meta-data. You can literally use TypeScript's powerful type-system to your full advantage in many small to super complex use-cases and let the framework handle all the validation, serialisation, and database stuff.And like that you are able to reuse types everywhere: From database to http router, configuration, RPC, API documentation, dependency injection, message broker, frontend, and more. That's not possible in this form in any other framework or when you combine lots of third-party libraries and glue them manually together. Deepkit separated the functionality in libraries which would allow you to use their features in Fastify and Prisma, too, but that would mean you lose one of the biggest advantage of all: Reusing types. |
When you start to hear people say of their PRs “it works but I just have to fix the types”, that usually means the codebase is trying to re-use too many types. In my experience.
The beauty of TypeScript is that you can have two different very narrowly specified types and the compiler will tell you if they’re compatible or not.
Reusing types is throwing away the most valuable feature of TypeScript.
For me, the type signature is part of the function signature and therefore should not be re-used.
Imagine what a disaster it would be if function signatures were reusable:
It’s too much. DRY can be taken too far.