9 ms·
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 applicati
by primering 4y ago
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.
- nerdponx 4y agoThis looks to me a lot like the FastAPI framework in Python. Thanks for the demo. > This does not work with Fastify and Prisma The point of Prisma is that it generates types for you from the Prisma schema, right? Are the generated types not interchangeable throughout the application?
- primering 4y agoThey surely can be used in the app, but a) requires that code generation and b) you are limited by the expressiveness of Prisma's DSL. The DSL supports much less than TypeScript. The last time I looked: No support for generics, conditional types, mapped types, (const) enums, index signatures, class instances (+methods), and more. Basically you lose the full power of TypeScript that way. Also you would have no way of using that interface in validation and serialisation, which is more important than just using it in type checking - you would need to rewrite the entity in another format like json-schema or zod-like libraries, which means at the end you still duplicate it.
- nerdponx 4y agoI was pretty sure that Prisma also supported generating OpenAPI schemas and GraphQL, but maybe I'm wrong. Fair point about expressiveness, but if your data must cross application boundaries then you are somewhat limited in expressiveness no matter what, because your types need to be understood by arbitrary clients. Lack of generics is pretty annoying though. It's probably my biggest annoyance with GraphQL.
- primering 4y agoGood point. That's why I'm working on a C++ version of TypeScript's type checking./computation so it can be used in all languages. That would allow us to use TypeScript types literally everywhere and remove that barrier. Prisma's DSL has no syntax for validation constraints, so even with openAPI generators it's incomplete.
- 4y ago