Settings

Font size
16px
Compact mode
v0.1.0 — Production-ready core

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.

42,927
req/sec · single process
63,782
req/sec · 4-worker cluster
~68ns
shared memory latency
5.1K
lines of source
3
prod dependencies
Philosophy

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.

NestJS structure, without the weight
Decorators, controllers, services, DI — in ~5K lines instead of 90K. No module boilerplate.
Faster than Hono, competitive with Elysia
Beats Hono by up to 10% on every endpoint. With clustering, exceeds Elysia's single-process throughput.
Batteries included, not mandatory
ORM, sessions, rate limiting, CORS, security headers, validation, WebSocket, uploads — all built-in, all optional.
Things nobody else has
Cross-worker shared memory. @Fn HTTP functions. @Go worker threads. @Command WebSocket routing. Param-name injection.
Design

Architecture at a Glance

Velocity is a single cohesive package. Every feature is built in, nothing requires external packages for common patterns.

Bun-first, Node-compatible
Optimized for Bun with a compatibility path for Node.js. Bun-exclusive features (shared memory, native mode) degrade gracefully.
Compile-once, run fast
Routes, middleware chains, validators, and injection stubs are all compiled at startup. Zero per-request overhead from framework internals.
3 production dependencies
joi, pg, and mysql2. No reflect-metadata, no Winston, no Express. The entire framework is auditable in an afternoon.
Convention over configuration
Sensible defaults for everything. Only port is required. Add features incrementally as your project grows.
Features

Core Framework

Click any card to see a code example.

Decorator Routing
@Get, @Post, @Put, @Delete, @Patch with fast trie-based lookup.
Auto-Discovery
velo.scan('./src') finds and registers everything. No manual wiring.
Constructor DI
Singleton and transient services, scoped child containers, circular dependency detection.
Built-in ORM
Prisma-like db.User.findAll() with SQLite, PostgreSQL, MySQL.
Param-Name Injection
create(body, user, session) — resolved by name, zero decorator noise.
Guards & Middleware
@Guards(fn) for auth. Middleware per-route or global. Lifecycle hooks.
Encrypted Sessions
Stateless cookie sessions with strong encryption. Zero overhead when unused.
Validation
@Validate with Joi, Zod, Yup, or plain functions. Auto-detected.
File Uploads
@Upload({ maxSize, maxFiles }) with native multipart parsing.
WebSocket Gateways
@WebSocket('/path') with @Command message routing.
Features

Performance & Scaling

Multi-Core Clustering
velo.cluster(4) — scale across CPU cores. Auto crash recovery.
Shared Memory
Cross-worker atomic data structures at ~68ns. 1,500x faster than Redis.
@Go Background Workers
Real worker threads with @Channel messaging. @Every and @Cron scheduling.
@Fn HTTP Functions
Call any method via GET /.name(args). No req/res boilerplate.
Security Built-in
CORS, cluster-aware rate limiting, security headers, signed cookies, timeouts.
Features

Tooling & DX

velogen CLI
Generates DB types, OpenAPI spec, typed client, env config, and API tester.
Envelocity
Typed, read-only .env wrapper with OrThrow getters.
Test Framework
@Suite / @Test / @Mock. In-process request testing with TestUtils.
Performance

Benchmarks

8-core Linux, Bun 1.3.13, oha c=32, 10s. Identical minimal JSON apps.

Single Process — GET /users (req/sec)

Elysia
62,872
Raw Bun
58,507
Velocity
42,927
Hono
39,148

All Endpoints

EndpointVelocityElysiaHonoRaw Bun
GET /users42,92762,87239,14858,507
GET /users/:id38,77262,56237,30258,116
POST /users29,75032,18827,41037,752

Cluster Scaling

Endpoint1 worker2 workers4 workers
GET /users35,98651,416 (+42%)63,782 (+77%)
GET /users/:id30,14844,573 (+47%)55,653 (+84%)
POST /users24,47435,257 (+44%)43,512 (+77%)
Key Takeaway At 4 workers, Velocity exceeds Elysia's single-process throughput. Shared memory ops run at ~68ns — 1,500x faster than Redis.
Landscape

Framework Comparison

CapabilityVelocityElysiaHonoNestJSExpress
Built-in DIYesNoNoYesNo
Built-in ORMYesNoNoNoNo
Encrypted sessionsBuilt-inPluginCustomCustomPackage
Shared memoryBuilt-inNoNoNoNo
Clusteringvelo.cluster()ExternalExternalExternalExternal
Code generationvelogenPluginExternal@nestjs/cliExternal
Background workers@Go + @ChannelNoNoNoNo
HTTP functions@FnNoNoNoNo
Prod deps34+1606+~9028+65
Source size~5.1K~12K~20K~90K~3K
Positioning

Why Not the Others?

Honest answers to the questions developers ask.

Why not NestJS?
Less ceremony, 2x lighter, faster, param-name injection, shared memory, built-in ORM — no module boilerplate.
Why not Fastify?
Comparable speed, but Velocity includes DI, ORM, sessions, and cross-worker shared memory out of the box.
Why not Hono?
Velocity is 11% faster, with DI, encrypted sessions, and shared memory. Hono wins on multi-runtime portability.
Why not Elysia?
Elysia wins single-process speed. Velocity wins on feature depth — and with clustering, exceeds Elysia's throughput.
Why not Express?
Express brings no structure, no performance, no built-in features. Velocity is faster with everything included.
When NOT to choose Velocity?
If you need multi-runtime (Deno, Edge Workers), pick Hono. If you need a massive plugin ecosystem today, pick NestJS or Express.
Step by Step

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
Tip Only 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
Param-name injection 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
Requirement Classes must use 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
Auto rate limiting In cluster mode on Bun, rate limiting shares counters across workers automatically.

Code Generation

CommandShortOutput
velogen typestDB type interfaces
velogen enveTyped .env config
velogen openapioaOpenAPI 3.1 spec
velogen clientcTyped fetch client
velogen apiaAPI testing UI
velogen allEverything
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.

Reference

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
Convention, not requirement velo.scan() works with any directory layout.
Reference

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
Reference

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 })