Announcing Vramework 0.3
I'm thrilled to announce Vramework version 0.3!
This update comes shortly after version 0.2 and focuses primarily on improving the developer experience. It introduces better API validation and reduces boilerplate, making it easier and more intuitive to build powerful APIs.
In a previous blog post, I mentioned my recent discovery of Encore.ts. It was both validating and a little intimidating to see the similarities between our frameworks. Encore.ts has plenty of benefits, such as built-in infrastructure setup, a server written in RustJS that offers major performance gains, and the ability to automatically split repositories into microservices.
In terms of where Vramework fits, I believe it lives somewhere between NestJS and Encore. Vramework requires less boilerplate than NestJS while still allowing developers to leverage existing open-source NodeJS libraries like uWS, Fastify, or Express. It can also be used in dispatch patterns with Next.js and similar frameworks.
Personally, I like having the ability to control my infrastructure with a declarative language like Terraform. I recognize that this is a matter of preference, and I understand the value of Encore.ts as a fully integrated solution. However, the goal of Vramework is to focus on one core task—API routing and HTTP documentation—and leave the rest to the user's preferences.
What Changed
TypeScript offers some powerful features that can be used to improve the developer experience beyond just type safety (as demonstrated by Kysely, my preferred SQL query builder).
With Vramework, we've leaned into TypeScript's compiler capabilities to automatically extract key information, which means we benefit from both stronger static type checking at compile time and improved runtime validation.
Improved API Developer Experience
In Vramework 0.2 and earlier, defining a route looked like this:
export const routes = [
route({
method: 'post',
route: '/blog/:blogId/comment',
schema: 'AddCommentToBlog',
func: addCommentToBlog,
permissions: {
canCommentOnBlog,
},
}),
];
The primary issue with this approach was that developers had to manually specify the schema
property for validation.
In Vramework 0.3+, we now use the following API:
import { addRoute } from './api';
addRoute({
method: 'post',
route: '/blog/:blogId/comment',
func: addCommentToBlog,
permissions: {
canCommentOnBlog,
}
});
This approach allows us to leverage more advanced TypeScript validation:
- The
AddCommentToBlog
schema is automatically inferred from the function's generic parameters. - The
blogId
parameter must exist withinAddCommentToBlog
. - The
query
property is an array of fields inAddCommentToBlog
, used for documentation purposes.
API Interfaces
Vramework now also generates an API interface, which can be used to apply type definitions in dispatchers like Next.js and other frameworks:
export type RoutesInterface =
{ route: '/books', method: 'post', input: null, output: Books } |
{ route: '/book', method: 'post', input: CreateBook, output: Book } |
{ route: '/book/:id', method: 'get', input: JustBookId, output: Book } |
{ route: '/book/:id', method: 'patch', input: null, output: Book } |
{ route: '/book/:id', method: 'delete', input: JustBookId, output: boolean };
This helps developers enforce consistency in API definitions and improves type safety across different parts of the application.
Addressing the Contradiction in the 0.2 Blog
In the 0.2 blog post, I wrote:
Vramework avoids the "magic" often found in other frameworks, where hidden processes can make debugging challenging. By providing explicit and transparent code structures, you gain full control over your application's behavior.
This statement reflects my dislike of using global state in codebases, especially singleton states, which conflicts with some popular Node.js frameworks.
Previously, we had developers export a routes
array, which would then be concatenated during the build process. This allowed developers to split routes into different files and choose which routes to run by importing them selectively—a pattern often used when deploying microservices.
However, this approach made TypeScript validation difficult. While exporting routes is still a common pattern in Node.js, it could become confusing as a long-term API decision. In Vramework 0.3, we've switched to using the addRoute
method, which allows for direct registration of routes, better validation, and a more cohesive developer experience.
I'm excited for you to try out Vramework 0.3 and see the improvements firsthand. The focus on reducing boilerplate, enhancing validation, and making APIs easier to work with is all aimed at making your development process smoother.
Let me know what you think! Are there features you'd like to see in future versions? Your feedback is always appreciated.