Build fast backends with zero bloat
Batteries-included TypeScript framework for Bun & Node.js — decorator routing, DI, built-in ORM, encrypted sessions, clustering, and shared memory in ~5K lines.
Why Velocity?
NestJS gives you structure but weighs 90K+ lines. Hono and Elysia are fast but leave you wiring everything yourself. Express is everywhere but has no opinions and no speed. Velocity gives you all of it — with none of the tradeoffs.
Architecture at a Glance
Velocity is a single cohesive package. Every feature is built in, nothing requires external packages for common patterns.
port is required. Add features incrementally as your project grows.Core Framework
Click any card to see a code example.
@Get, @Post, @Put, @Delete, @Patch with fast trie-based lookup.velo.scan('./src') finds and registers everything. No manual wiring.db.User.findAll() with SQLite, PostgreSQL, MySQL.create(body, user, session) — resolved by name, zero decorator noise.@Guards(fn) for auth. Middleware per-route or global. Lifecycle hooks.@Validate with Joi, Zod, Yup, or plain functions. Auto-detected.@Upload({ maxSize, maxFiles }) with native multipart parsing.@WebSocket('/path') with @Command message routing.Performance & Scaling
velo.cluster(4) — scale across CPU cores. Auto crash recovery.@Channel messaging. @Every and @Cron scheduling.GET /.name(args). No req/res boilerplate.Tooling & DX
.env wrapper with OrThrow getters.@Suite / @Test / @Mock. In-process request testing with TestUtils.Benchmarks
8-core Linux, Bun 1.3.13, oha c=32, 10s. Identical minimal JSON apps.
Single Process — GET /users (req/sec)
All Endpoints
| Endpoint | Velocity | Elysia | Hono | Raw Bun |
|---|---|---|---|---|
| GET /users | 42,927 | 62,872 | 39,148 | 58,507 |
| GET /users/:id | 38,772 | 62,562 | 37,302 | 58,116 |
| POST /users | 29,750 | 32,188 | 27,410 | 37,752 |
Cluster Scaling
| Endpoint | 1 worker | 2 workers | 4 workers |
|---|---|---|---|
| GET /users | 35,986 | 51,416 (+42%) | 63,782 (+77%) |
| GET /users/:id | 30,148 | 44,573 (+47%) | 55,653 (+84%) |
| POST /users | 24,474 | 35,257 (+44%) | 43,512 (+77%) |
Framework Comparison
| Capability | Velocity | Elysia | Hono | NestJS | Express |
|---|---|---|---|---|---|
| Built-in DI | Yes | No | No | Yes | No |
| Built-in ORM | Yes | No | No | No | No |
| Encrypted sessions | Built-in | Plugin | Custom | Custom | Package |
| Shared memory | Built-in | No | No | No | No |
| Clustering | velo.cluster() | External | External | External | External |
| Code generation | velogen | Plugin | External | @nestjs/cli | External |
| Background workers | @Go + @Channel | No | No | No | No |
| HTTP functions | @Fn | No | No | No | No |
| Prod deps | 3 | 4+16 | 0 | 6+~90 | 28+65 |
| Source size | ~5.1K | ~12K | ~20K | ~90K | ~3K |
Why Not the Others?
Honest answers to the questions developers ask.
Tutorial
Build your first Velocity project. Assumes TypeScript + Bun/Node knowledge.
Installation
Clone the framework and link it:
# Clone & build
git clone https://github.com/your-org/velocity-framework.git
cd velocity-framework
bun install && bun run build && bun run sync
# Create your project
mkdir my-app && cd my-app && bun init
terminal
Required tsconfig.json:
{ "compilerOptions": { "experimentalDecorators": true, "emitDecoratorMetadata": true, "target": "ESNext", "module": "ESNext", "moduleResolution": "bundler" } }
tsconfig.json
Application Setup
import { VeloApplication } from '@velocity/framework';
export const velo = new VeloApplication({
port: 5000,
globalPrefix: '/api',
cors: { origin: '*' },
rateLimit: { max: 100, window: 60_000 },
security: true,
shutdown: { timeout: 10_000, auto: true },
});
velo.ts
port is required. Add features as you need them.Database & Entities
export const db = DB({ type: 'sqlite', database: ':memory:', filename: ':memory:' });
db.ts
@Entity('users')
export class User {
@PrimaryKey() id: number;
@Column() name: string;
@Column({ unique: true }) email: string;
constructor() { this.id = 0; this.name = ''; this.email = ''; }
}
src/entities/user.entity.ts
Supported: 'sqlite', 'postgresql', 'mysql'. For multiple databases: @Entity('posts', 'pgDb').
Controllers & Routes
@Controller('/users')
export class UserController {
@Get('/')
async list() { return { users: await db.User.findAll() }; }
@Get('/:id')
async getById(param: any) { return db.User.findById(param.id); }
@Post('/')
async create(body: any) { return db.User.create(body); }
@Delete('/:id')
async remove(param: any) { await db.User.delete(param.id); }
}
src/controllers/user.controller.ts
body, param, query, session, user, req, res injected by name — no decorators needed.Nest controllers: @Controller('/profile', { scope: 'UserController' })
Services & DI
@Service()
export class AuthService {
createToken(id: number) { return `token-${id}`; }
}
@Controller('/auth')
export class AuthController {
constructor(private auth: AuthService) {}
@Post('/login')
login(body: any) { return { token: this.auth.createToken(body.id) }; }
}
Constructor injection
Singletons by default. Scope with @Service({ scope: 'ControllerName' }).
Auto-Discovery
async function main() {
await velo.scan(path.join(__dirname, 'src'), { databases: { db } });
await velo.listen();
}
main.ts
export class to be discovered.Filter: { include: ['UserController'], exclude: ['MetricsController'] }
Manual velo.register() is still available for tests and dynamic cases.
Middleware & Guards
// Guard: return false = 403
@Guards((req) => !!req.headers['authorization'])
// Middleware: classic (req, res, next)
@Middlewares((req, res, next) => { console.log(req.url); next(); })
// Global hooks
velo.onRequest((req) => { ... });
velo.onResponse((req, res) => { ... });
velo.onError((err, req, res) => { res.status(500).json({ error: err.message }); });
Guards, middleware, hooks
Validation
@Validate(Validator.createSchema({ name: Joi.string().required() })) // Joi
@Validate(z.object({ name: z.string() })) // Zod
@Validate((body) => { if(!body.name) throw Error('!'); return body; }) // Function
Validation options
Sessions & Cookies
// Enable in config
session: { secret: 'key', maxAge: 3600 }
// In handlers
login(body, session) { session.set({ userId: body.id }); }
me(session) { return session.data; }
logout(session) { session.destroy(); }
// Cookies
res.setCookie('token', 'val', { httpOnly: true, signed: true });
Sessions & cookies
Uploads, WebSocket, Workers, @Fn
// File uploads
@Upload({ maxSize: 5_242_880 })
upload(req) { return req.files.avatar; }
// WebSocket
@WebSocket('/chat')
class Chat { @Command('msg') handle(ws, d) { ws.send(...); } }
// Background worker
@Go() async run(@Channel('jobs') ch) { for await(const j of ch) { ... } }
// HTTP functions
@Fn() greet(name) { return { hi: name }; } // GET /.greet("Alice")
// Scheduling
@Every(60_000) tick() { ... }
@Cron('0 * * * *') hourly() { ... }
More features
Clustering & Shared Memory
velo.cluster(4); // instead of velo.listen()
// Cross-worker shared state (Bun only)
class M extends SharedState {
@Field('counter') hits!: SharedCounter;
@Field('hashmap', { capacity: 4096 }) ips!: SharedHashMap;
}
// Inject via DI, use atomic ops
this.m.hits.add(1); // ~68ns, all workers see it
Cluster + shared memory
Code Generation
| Command | Short | Output |
|---|---|---|
velogen types | t | DB type interfaces |
velogen env | e | Typed .env config |
velogen openapi | oa | OpenAPI 3.1 spec |
velogen client | c | Typed fetch client |
velogen api | a | API testing UI |
velogen all | — | Everything |
npm run velogen -- all ./my-app
terminal
Testing
@Suite('User API')
class Tests {
@BeforeEach
setup() { this.app = TestUtils.createTestApp(); this.app.register(UserController); }
@Test('lists users')
async list() {
const { status } = await TestUtils.makeRequest(this.app, { method: 'GET', path: '/users' });
expect(status).toBe(200);
}
}
Decorator-based tests
TestUtils.makeRequest runs in-process — no network, no server startup.
Project Structure
my-app/
velo.ts - App instance + config
db.ts - Database connection(s)
main.ts - Entry point
src/
controllers/ - @Controller classes
entities/ - @Entity definitions
services/ - @Service business logic
state/ - SharedState (cluster)
gateways/ - @WebSocket gateways
velo/ - Generated (velogen)
tests/
public/
Recommended layout
velo.scan() works with any directory layout.Configuration
new VeloApplication({
port: 3000,
globalPrefix: '/api',
runtimeMode: 'compat', // 'compat' | 'bun-native'
cors: { origin: '*' },
security: true,
rateLimit: { max: 100, window: 60_000 },
cookieSecret: 'hmac-key',
session: { secret: 'aes-key', maxAge: 3600 },
compression: { enabled: true, threshold: 1024 },
shutdown: { timeout: 10_000, auto: true },
});
All options
ORM API
await db.User.findAll();
await db.User.findById(1);
await db.User.findOne({ email: 'alice@ex.com' });
await db.User.create({ name: 'Alice' });
await db.User.update(1, { name: 'Bob' });
await db.User.delete(1);
await db.User.query()
.select('name').where('age > ?', 18)
.orderBy('name').limit(10).execute();
CRUD + Query builder
Custom drivers: registerDriver('libsql', { connect, query, execute, close })