Most backend frameworks today are built on foundations from a different era. Express sits on Node.js HTTP. NestJS sits on Express. Each layer adds abstraction, dependencies, and constraints. What if we started fresh?
YasuiJS is a new TypeScript framework for building REST APIs, built on Web Standards from day one. No Express underneath. No Node.js HTTP layer. Just the Fetch API, Web Standards Request/Response, and a lightweight server adapter (srvx) that works across runtimes.
The result? A framework that runs anywhere—Node.js, Deno, Bun, Cloudflare Workers, Vercel Edge—with the elegant decorator-driven DX you'd expect from modern frameworks, but without the bloat.
Here's what it looks like:
@Controller('/api/users')
export class UserController {
constructor(private userService: UserService) {}
@Get('/:id')
@ApiOperation('Get user by ID')
async getUser(
@Param('id') id: number,
@Query('active') active: boolean
) {
return this.userService.getUserById(id, active);
}
}
If you've used NestJS, this syntax feels familiar. That's intentional—YasuiJS takes inspiration from patterns that work. Class-based, decorators.
Built on Web Standards, Not Legacy Layers
YasuiJS is not built on top of Express or Node.js HTTP. It's built directly on the Fetch API and Web Standards Request/Response.
The Architecture Difference
Traditional Stack:
Your Code → NestJS → Express → Node.js HTTP
YasuiJS Stack:
Your Code → YasuiJS → srvx (Web Standards adapter) → Runtime (Node/Deno/Bun)
Web Standards are platform-agnostic. The same Request/Response objects work on Node.js, Deno, Bun, Cloudflare Workers, Vercel Edge, AWS Lambda@Edge, anywhere that implements the Fetch API.
Multi-Runtime Support
Because YasuiJS is built on Web Standards from day one, multi-runtime support isn't bolted on—it's inherent.
Write your API once:
@Controller('/api/products')
export class ProductController {
constructor(private productService: ProductService) {}
@Get('/')
getProducts() {
return this.productService.findAll();
}
}
Deploy it anywhere:
// Node.js / Bun
yasui.createServer({ controllers: [ProductController], port: 3000 });
// Deploy to Cloudflare Workers
export default { fetch: app.fetch };
// Deploy to Vercel Edge
export const GET = app.fetch;
export const POST = app.fetch;
// Deploy to Deno Deploy
Deno.serve(app.fetch);
Same code. Same imports. Same dependencies. Different runtime, zero changes.
This matters because:
- Edge computing is moving REST APIs closer to users
- Deno and Bun are production-ready alternatives to Node.js
- Serverless platforms increasingly support Web Standards
- Future runtimes will support Fetch API, not Express
NestJS can't do this. It's fundamentally tied to Node.js and Express's architecture. YasuiJS was designed for a multi-runtime world from the start.
Flexibility Without Boilerplate
No Module System
YasuiJS doesn't have modules. Not because modules are bad, but because for REST APIs, they're unnecessary overhead.
In NestJS, adding a new endpoint means:
- Create the service
- Add it to a module's providers
- Import that module wherever you need it
- Export if other modules need it
- Update the dependency graph
In YasuiJS:
- Create the service with
@Injectable() - Inject it in your controller
That's it. The DI system resolves the dependency graph automatically. No modules, no providers arrays, no imports/exports to manage.
@Injectable()
class UserService {
// Business logic
}
@Controller('/users')
class UserController {
constructor(private userService: UserService) {} // Just works
}
yasui.createServer({ controllers: [UserController] });
Automatic Type Casting Everywhere
YasuiJS reads your TypeScript types and automatically converts parameters at runtime. Not just in controllers—everywhere you use parameter decorators.
// In controllers
@Get('/:id')
getUser(
@Param('id') id: number, // Converted to number
@Query('active') active: boolean // Converted to boolean
) { }
// In middlewares (same pattern!)
@Middleware()
class RateLimiter {
use(
@Query('apiKey') key: string,
@Header('x-rate-limit') limit: number // Also converted!
) { }
}
No pipes to configure for basic type casting. But when you need custom validation or transformation, YasuiJS provides Pipes that can be applied at global, controller, or method level—works seamlessly with class-validator.
Consistent Patterns Everywhere
In YasuiJS, middlewares work exactly like controllers. Same decorators. Same DI. Same parameter extraction. Same type casting.
@Middleware()
export class AuthMiddleware {
constructor(private authService: AuthService) {} // DI works
use(
@Header('authorization') token: string, // Decorators work
@Query('version') version: number, // Type casting works
@Inject() logger: LoggerService // Method injection works
) {
if (!this.authService.verify(token)) {
throw new HttpError(401, 'Unauthorized');
}
logger.log(`Auth successful, API v${version}`);
}
}
You learn one pattern—controllers—and it works everywhere. One mental model for the entire framework.
Performance: A Result of Clean Architecture
When you eliminate abstraction layers and build on modern foundations, performance follows naturally.
Benchmark setup: Identical REST API (3 controllers, 9 endpoints, full DI), Node.js v22, Windows 11, 10-second load test per endpoint:
| Framework | Requests/sec | Avg Latency | Cold Start |
|---|---|---|---|
| YasuiJS | 6,850 | 0.98ms | 247ms |
| NestJS | 5,442 | 1.31ms | 582ms |
| Express | 5,505 | 1.31ms | 15ms |
YasuiJS is 25% faster than NestJS in request throughput and more than 50% faster to cold start (critical for serverless).
Why?
- No Express layer: Direct to Web Standards reduces overhead
- Radix3 router: Efficient route matching algorithm
- Cached DI resolution: Dependencies resolved once at startup
- Pre-computed metadata: Decorators processed during initialization, not per-request
Clean architecture produces fast code. It's not about micro-optimizations—it's about fewer layers doing less work.
What YasuiJS Is (And Isn't)
YasuiJS Is For:
- REST APIs: HTTP endpoints, CRUD operations, RESTful services
- Multi-runtime deployment: Need to run on Node, Deno, Bun, or edge without code changes
- Modern architectures: Serverless, edge computing, distributed systems
- Clean patterns: Class-based, decorator-driven, dependency injection
- TypeScript-first: Full type safety with automatic runtime type conversion
- Performance-critical apps: Where cold start and throughput matter
YasuiJS Is Not:
- Not a full framework: No built-in GraphQL, WebSockets, or microservices (REST focus)
- Not batteries-included: You bring your own database, validation, authentication libraries
- Not trying to replace NestJS: Different goals, different trade-offs
Choose YasuiJS When:
- You're building REST APIs specifically
- You need multi-runtime flexibility (Node/Deno/Bun/edge)
- You want NestJS-style patterns without the module system
- Performance and bundle size are constraints (serverless/edge scenarios)
- You prefer focused tools over comprehensive frameworks
Choose NestJS When:
- You need more than REST (GraphQL, WebSockets, CQRS, microservices)
- You want comprehensive integrations (Passport, TypeORM, Prisma, Bull, etc.)
- You're building large enterprise applications with complex requirements
- You need mature ecosystem and large community support
Getting Started
Install YasuiJS (and why not with Bun !):
bun install yasui
Configure TypeScript (enable experimentalDecorators and emitDecoratorMetadata):
{
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
// ... other options
}
}
Create your first API:
import yasui, { Controller, Get, Post, Body, Injectable } from 'yasui';
@Injectable()
class UserService {
private users = [{ id: 1, name: 'Alice' }];
getAll() { return this.users; }
create(name: string) {
const user = { id: this.users.length + 1, name };
this.users.push(user);
return user;
}
}
@Controller('/api/users')
class UserController {
constructor(private userService: UserService) {}
@Get('/')
getUsers() {
return this.userService.getAll();
}
@Post('/')
createUser(@Body('name') name: string) {
return this.userService.create(name);
}
}
yasui.createServer({
controllers: [UserController],
swagger: { generate: true, path: '/docs' }
});
Run it:
bun run app.ts
Visit http://localhost:3000/api/users for your API.
Visit http://localhost:3000/docs for auto-generated Swagger UI.
That's it. No modules to configure. No providers to declare. Just controllers and services.
Key Features at a Glance
Beyond what we've covered, YasuiJS includes:
- Automatic Swagger/OpenAPI generation - Document as you code with flexible decorators
- Built-in services - LoggerService, ConfigService ready to use
- Flexible DI scopes - SHARED (singleton), LOCAL (per-request), DEEP_LOCAL (isolated)
- TLS/HTTPS & HTTP/2 support - Production-ready security out of the box
- Express-compatible Request - Web Standards with familiar Express properties for easy migration
- Comprehensive error handling - Throw anywhere, framework catches and formats
- Method-level injection - Inject dependencies at constructor or method level
- Validation pipes - Custom validation/transformation at any level, integrates with class-validator
The Future of REST APIs
The web platform is evolving. Edge computing brings computation closer to users. Deno and Bun offer modern alternatives to Node.js. Web Standards provide a common foundation across all of them.
YasuiJS was built for this future. Not as a reaction to existing frameworks, but as a ground-up rethinking of what a REST framework should be in a multi-runtime world.
If you're building REST APIs and want:
- Modern architecture based on Web Standards
- Multi-runtime flexibility without code changes
- Clean patterns without unnecessary boilerplate
- Performance that scales from edge to cloud
YasuiJS might be what you're looking for.
Learn More
- 🌐 Documentation: yasui.app
- 📦 npm:
npm install yasui - 💻 GitHub: github.com/thomasbarkats/yasui
- 📚 Getting Started: yasui.app/guide/getting-started
YasuiJS is open source (AGPL-3.0) and actively developed. Try it for your next REST API project and see if the approach resonates with you.
What are your thoughts on Web Standards vs framework-specific abstractions? Are you deploying to edge runtimes? Let me know in the comments.
Top comments (0)