π₯ DUAL RUNTIME: FastAPI for Node.js AND Bun - Same Code, Maximum Performance | β‘ 3.8x Faster with Bun
npm install syntrojs
![]()
FastAPI for Node.js & Bun
β‘ 3.8x faster with Bun | π 89.5% of Fastify with Node.js






---
β SyntroJS is production-ready with a battle-tested core and comprehensive feature set. The framework is stable and ready for production use.
- β
Battle-tested Core - 980+ tests across Node.js and Bun (100% passing)
- β
CORS Fully Functional - REST and Lambda modes with comprehensive validation
- β
AWS Lambda Support - Production-ready Lambda mode with full API Gateway integration
- β
Router System - Group routes with prefixes and router-level middleware
- β
Type-Safe Client - Testing and frontend integration with autocomplete
- β
Active development - Regular updates and bug fixes
- π― v0.8.0 planned - Security & Real-time Features
Latest Release: v0.8.0 - Security Features (CSRF, JWT Refresh) + Real-time (SSE) - CHANGELOG
---
SyntroJS is the world's first dual-runtime framework that brings the simplicity and developer experience of FastAPI to the TypeScript ecosystem. Write your code once and run it on either Node.js for stability or Bun for maximum performance.
Key Highlights:
- π Dual Runtime: Same code runs on Node.js and Bun
- βοΈ AWS Lambda: Same code works in REST (dev) and Lambda (prod) modes
- π₯ FastAPI DX: Automatic validation, type safety, elegant error handling
- π¨ Auto Docs: Interactive Swagger UI + ReDoc out of the box
- π§ͺ Testing: SmartMutator for mutation testing in seconds
---
``bash`
npm install syntrojs zod
`javascript
import { SyntroJS } from 'syntrojs';
import { z } from 'zod';
const app = new SyntroJS({ title: 'My API' });
// Simple GET endpoint
app.get('/hello', {
handler: () => ({ message: 'Hello World!' })
});
// POST with automatic validation
app.post('/users', {
body: z.object({
name: z.string().min(1),
email: z.string().email(),
}),
handler: ({ body }) => ({ id: 1, ...body }),
});
await app.listen(3000);
`
That's it! π Visit http://localhost:3000/docs for interactive documentation.
Same code, Lambda deployment - Just change one flag:
`javascript
import { SyntroJS } from 'syntrojs';
import { z } from 'zod';
// Lambda mode: rest: false
const app = new SyntroJS({ rest: false, title: 'My API' });
app.post('/users', {
body: z.object({
name: z.string().min(1),
email: z.string().email(),
}),
handler: ({ body }) => ({ id: 1, ...body }),
});
// Export handler for AWS Lambda
export const handler = app.handler();
`
That's it! π Deploy to AWS Lambda. Same validation, same type safety, same code.
See Lambda Usage Guide for complete examples.
---
. Full API Gateway integration with automatic event detection and CORS support.Lambda Adapters Status:
- β
API Gateway: Implemented
- β
SQS: Implemented
- β
S3: Implemented
- β
EventBridge: Implemented
$3
Automatic validation with Zod, full TypeScript type safety, elegant error handling (HTTPException).$3
Beautiful landing page + Swagger UI + ReDoc out of the box at /docs.$3
SmartMutator for mutation testing in seconds. Type-safe client for testing and frontend integration.$3
Middleware system, WebSockets, dependency injection, background tasks, structured logging.$3
JWT, OAuth2, API Keys, and security plugins built-in.$3
Lambda adapters follow SOLID principles and can be extracted to separate packages. Test adapters independently without full framework.---
π Examples
examples/ directory:$3
- Quick Start - Basic API example with tests
- Dual Runtime - Same code on Node.js and Bun$3
- HTTP Methods - Redirects, content negotiation, HEAD/OPTIONS
- Middleware - Global and path-specific middleware
- WebSockets - Real-time communication
- Documentation Config - Customizing OpenAPI docs$3
- Lambda Example - Complete AWS Lambda deployment
- Bun Runtime - Bun-specific optimizations
- Demo Brutal - Performance benchmarks$3
Check the examples README for a complete list.---
π₯ Dual-Runtime: Node.js vs Bun
Same code, different runtimes:
`bash
Node.js (stability + full ecosystem)
node app.jsBun (maximum performance)
bun app.js
`| Runtime | Performance | Tests Passing | Use Case |
|---------|-------------|---------------|----------|
| Node.js | 89.5% of Fastify (5,819 req/s) | 728/728 (100%) | Production, plugins |
| Bun | 3.8x faster (~22,000 req/s) | 458/487 (94%) | Maximum speed |
$3
| Feature | Node.js | Bun | Status |
|---------|---------|-----|--------|
| Core API | β
Full | β
Full | Identical |
| Plugins (CORS, Helmet, etc.) | β
Full | β
Full | Production ready |
| Static files | β
Full | β Not available | v0.9.0 planned |
|
getRawFastify() | β
Works | β Use getRawServer() | - |---
π‘ Core Concepts
$3
Automatic validation with Zod schemas:
`javascript
app.post('/users', {
body: z.object({
name: z.string().min(1),
email: z.string().email(),
}),
params: z.object({
id: z.string().uuid(),
}),
query: z.object({
page: z.coerce.number().min(1).default(1),
}),
handler: ({ body, params, query }) => {
// All validated and type-safe!
return { ...body, id: params.id, page: query.page };
},
});
`$3
Share services across routes:
`javascript
import { inject } from 'syntrojs';const dbService = inject(() => new Database(), { scope: 'singleton' });
app.get('/users', {
dependencies: { db: dbService },
handler: ({ dependencies }) => dependencies.db.getUsers()
});
`$3
FastAPI-style exceptions:
`javascript
import { NotFoundException, BadRequestException } from 'syntrojs';app.get('/users/:id', {
handler: ({ params }) => {
const user = findUser(params.id);
if (!user) {
throw new NotFoundException('User not found');
}
return user;
},
});
`$3
Global or path-specific:
`javascript
// Global middleware
app.use(async (ctx, next) => {
console.log(${ctx.method} ${ctx.path});
await next();
});// Path-specific
app.use('/api/*', async (ctx, next) => {
if (!ctx.headers.authorization) {
throw new UnauthorizedException('Token required');
}
await next();
});
`$3
Non-blocking async tasks:
`javascript
app.post('/users', {
handler: ({ body, background }) => {
// Queue email send (non-blocking)
background.addTask(async () => {
await sendWelcomeEmail(body.email);
});
return { success: true };
}
});
`---
π API Reference
$3
`typescript
new SyntroJS(config?: SyntroJSConfig)
`Configuration Options:
| Option | Type | Default | Description |
|--------|------|---------|-------------|
|
title | string | - | API title for OpenAPI docs |
| version | string | - | API version |
| description | string | - | API description |
| runtime | 'auto' \| 'node' \| 'bun' | 'auto' | Force specific runtime |
| rest | boolean | true | REST mode (HTTP server) or Lambda mode |
| docs | boolean \| object | true | Configure documentation endpoints |
| logger | boolean | false | Enable Fastify logger |
| syntroLogger | boolean \| object | false | Enable @syntrojs/logger |$3
`typescript
app.get(path: string, config: RouteConfig)
app.post(path: string, config: RouteConfig)
app.put(path: string, config: RouteConfig)
app.delete(path: string, config: RouteConfig)
app.patch(path: string, config: RouteConfig)
`Route Config:
`typescript
interface RouteConfig {
handler: (ctx: RequestContext) => any;
body?: ZodSchema; // Request body validation
params?: ZodSchema; // Path parameters validation
query?: ZodSchema; // Query parameters validation
response?: ZodSchema; // Response validation
status?: number; // Default status code
dependencies?: object; // Dependency injection
}
`$3
REST Mode:
`typescript
// Start server
const address = await app.listen(port: number, host?: string);// Stop server
await app.close();
`Lambda Mode:
`typescript
// Export handler
export const handler = app.handler();
`$3
`typescript
interface RequestContext {
method: HttpMethod; // HTTP method
path: string; // Request path
params: any; // Path parameters (validated)
query: any; // Query parameters (validated)
body: any; // Request body (validated)
headers: Record; // Request headers
cookies: Record; // Cookies
correlationId: string; // Request tracking ID
timestamp: Date; // Request timestamp
dependencies: Record; // Injected dependencies
background: {
addTask(task: () => void): void; // Queue background task
};
download(data, options): FileDownloadResponse; // File download helper
redirect(url, statusCode?): RedirectResponse; // Redirect helper
accepts: AcceptsHelper; // Content negotiation helper
}
`---
π Security
$3
`typescript
import { OAuth2PasswordBearer } from 'syntrojs';const oauth2 = new OAuth2PasswordBearer({ tokenUrl: '/token' });
app.get('/protected', {
dependencies: { user: oauth2 },
handler: ({ dependencies }) => ({ user: dependencies.user }),
});
`$3
`typescript
import { HTTPBearer } from 'syntrojs';const bearer = new HTTPBearer();
app.get('/api/data', {
dependencies: { token: bearer },
handler: ({ dependencies }) => ({ data: 'protected' }),
});
`$3
`typescript
import { APIKeyHeader, APIKeyQuery } from 'syntrojs';// Via header
const apiKeyHeader = new APIKeyHeader({ name: 'X-API-Key' });
// Via query parameter
const apiKeyQuery = new APIKeyQuery({ name: 'api_key' });
app.get('/api/data', {
dependencies: { apiKey: apiKeyHeader },
handler: ({ dependencies }) => ({ data: 'protected' }),
});
`$3
`typescript
import { HTTPBasic } from 'syntrojs';const basicAuth = new HTTPBasic();
app.get('/admin', {
dependencies: { credentials: basicAuth },
handler: ({ dependencies }) => {
const { username, password } = dependencies.credentials;
return { admin: true };
},
});
`---
π§ͺ Testing
$3
`javascript
import { describe, test, expect, beforeAll, afterAll } from 'vitest';
import { app } from './app';describe('API Tests', () => {
let server: string;
beforeAll(async () => {
server = await app.listen(0); // Random port
});
afterAll(async () => {
await app.close();
});
test('POST /users creates a user', async () => {
const port = new URL(server).port;
const res = await fetch(
http://localhost:${port}/users, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: 'John', email: 'john@example.com' })
});
const data = await res.json();
expect(res.status).toBe(200);
expect(data.name).toBe('John');
});
});
`$3
Test your API without starting a server, or use it in your frontend:
`typescript
import { createClient } from 'syntrojs';
import type { App } from './app';// Local mode (testing) - executes handlers directly
const client = createClient(app, { mode: 'local' });
// Call routes with autocomplete
const response = await client.users.get();
const user = await client.users[':id'].get({ params: { id: '123' } });
const created = await client.users.post({
body: { name: 'John', email: 'john@example.com' }
});
// Remote mode (frontend) - makes HTTP requests
const apiClient = createClient(app, {
mode: 'remote',
baseUrl: 'https://api.example.com'
});
`See Type-Safe Client Documentation for complete examples.
$3
`bash
pnpm test:mutate
`| Method | Mutants | Tests | Time |
|--------|---------|-------|------|
| Stryker (vanilla) | 1,247 | 187,050 | 43 min |
| SmartMutator | 142 | 284 | 12 sec |
Mutation Score: 58.72% (742 killed, 144 survived)
---
π Quality Metrics
- Tests: 1,019+ passing (Node.js 100%, Bun 94%)
- Coverage: 71.55% (Branch: 80.73%)
- Mutation Score: 58.72%
- Code Quality: 100% SOLID + DDD + Functional Programming
- Top Performers: RouteRegistry (100%), ZodAdapter (100%), DependencyInjector (95.83%)
---
π Production & Security
`javascript
const app = new SyntroJS({
title: 'Production API',
docs: false // β
REQUIRED for production
});
`$3
- [ ] Disable all documentation (
docs: false)
- [ ] Set proper CORS origins (not *)
- [ ] Enable rate limiting
- [ ] Configure structured logging without sensitive data
- [ ] Use environment variables for secrets---
π TOON Format (v0.5.0+)
Reduce API bandwidth costs 40-60% while keeping responses human-readable.
| Feature | JSON | TOON π― | gRPC/Protobuf |
|---------|------|------------|---------------|
| Payload Size | 100% | 40-50% β‘ | 35-45% |
| Human-Readable | β
Yes | β
Yes | β Binary |
| Debug with curl | β
Easy | β
Easy | β Requires tools |
| Setup Time | 5 minutes | 5 minutes | 2+ hours |
| Tooling Needed | None | None | protoc, plugins |
TOON gives you the best of both worlds: gRPC's efficiency with JSON's simplicity.
See TOON Format Documentation for details.
---
πΊοΈ Roadmap
$3
- [x] File downloads, streaming, uploads
- [x] HTTP redirects, content negotiation$3
- [x] TOON Format Support (40-60% payload reduction)
- [x] Serialization Architecture Refactor$3
- [x] Lambda Mode (rest: false)
- [x] API Gateway Integration
- [x] Dynamic Routes with pattern matching
- [x] 82 Lambda tests passing$3
- [x] SyntroRouter - Group endpoints with prefixes
- [x] Router-level middleware (router.use())
- [x] app.include(router) - Include routers in app
- [x] Type-Safe Client (createClient) - Testing & frontend integration
- [x] Serializer Chain of Responsibility with next()
- [x] Serializer Priority System
- [x] Serializer Helper Methods (registerBefore, registerAfter, registerFirst)
- [x] SyntroRouter - Group endpoints with prefixes
- [x] Router-level middleware (router.use())
- [x] app.include(router) - Include routers in app
- [x] Type-Safe Client (createClient) - Testing & frontend integration
- [x] Serializer Chain of Responsibility with next()
- [x] Serializer Priority System
- [x] Serializer Helper Methods (registerBefore, registerAfter, registerFirst)$3
- [x] CSRF protection
- [x] JWT Refresh Tokens
- [x] Server-Sent Events (SSE)
- [ ] WebSocket rooms/namespaces
- [ ] Session management$3
- [ ] Static file serving _(optional)_
- [ ] Template rendering integrations _(optional)_
- [ ] Native Bun plugins$3
- [ ] Official CLI (create-syntrojs`)---
- Router System - Group routes with prefixes and middleware
- Type-Safe Client - Testing and frontend integration guide
- Lambda Usage Guide - Complete Lambda documentation
- Lambda Architecture - Adapter architecture guide
- Architecture - Framework architecture
- Examples - Code examples and demos
- CHANGELOG - Version history
---
We welcome contributions! Check out our GitHub repository.
Apache 2.0 - See LICENSE for details.