Getting Started with OneBun
Prerequisites
- Bun.js 1.2.12+ (not Node.js)
- TypeScript knowledge
- Basic understanding of decorators
Step 1: Project Setup
The fastest way to start is with the CLI:
bun create @onebun my-onebun-app
cd my-onebun-app
bun run devThis creates a ready-to-run minimal app — src/index.ts, src/config.ts (typed PORT/HOST), src/app.module.ts and an example AppController/AppService answering GET / — plus tsconfig.json, .env.example and a docker-compose.yml with Postgres + Redis, with dependencies already installed. bun run dev starts it.
The rest of this guide builds the hello module from scratch. Follow it either on top of the generated project or from a manual setup:
mkdir my-onebun-app
cd my-onebun-app
bun init -y
bun add @onebun/coreAll other @onebun/* packages (logger, envs, requests, etc.) are transitive dependencies of @onebun/core. Add optional packages as needed: @onebun/drizzle for database, @onebun/cache for caching, @onebun/nats for NATS queues.
Step 2: TypeScript Configuration
Create tsconfig.json:
{
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "bundler",
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"skipLibCheck": true,
"declaration": true,
"declarationMap": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"types": ["bun-types"]
},
"include": ["src/**/*"],
"exclude": ["node_modules"]
}Critical: experimentalDecorators and emitDecoratorMetadata must be true.
Step 3: Create Environment Schema
src/config.ts:
import { Env, type InferConfigType } from '@onebun/core';
export const envSchema = {
server: {
port: Env.number({ default: 3000, env: 'PORT' }),
host: Env.string({ default: '0.0.0.0', env: 'HOST' }),
},
app: {
name: Env.string({ default: 'my-onebun-app', env: 'APP_NAME' }),
debug: Env.boolean({ default: true, env: 'DEBUG' }),
},
};
// Required: enables typed this.config.get() in services and controllers
export type AppConfig = InferConfigType<typeof envSchema>;
declare module '@onebun/core' {
interface OneBunAppConfig extends AppConfig {}
}Step 4: Create a Service
src/hello.service.ts:
import { BaseService, Service } from '@onebun/core';
@Service()
export class HelloService extends BaseService {
private greetCount = 0;
/**
* Generate a greeting message
*/
greet(name: string): string {
this.greetCount++;
this.logger.info('Generating greeting', { name, count: this.greetCount });
return `Hello, ${name}! You are visitor #${this.greetCount}`;
}
/**
* Get total greet count
*/
getCount(): number {
return this.greetCount;
}
}Key Points:
@Service()decorator registers the class for DIBaseServiceprovidesthis.loggerandthis.config- Constructor receives dependencies automatically
Step 5: Create Validation Schema
src/hello.schema.ts:
import { type } from '@onebun/core';
export const greetBodySchema = type({
name: 'string',
'message?': 'string',
});
export type GreetBody = typeof greetBodySchema.infer;Schemas live in separate files. Export both the schema (for runtime validation) and the inferred type (for controller signatures).
Step 6: Create a Controller
src/hello.controller.ts:
import {
BaseController,
Controller,
Get,
Post,
Param,
Body,
} from '@onebun/core';
import { HelloService } from './hello.service';
import { greetBodySchema, type GreetBody } from './hello.schema';
@Controller('/api/hello')
export class HelloController extends BaseController {
constructor(private helloService: HelloService) {
super();
}
@Get('/')
async hello() {
this.logger.info('Hello endpoint called');
return { message: 'Hello from OneBun!' };
}
@Get('/stats')
async stats() {
return {
totalGreets: this.helloService.getCount(),
uptime: process.uptime(),
};
}
@Get('/:name')
async greetByPath(@Param('name') name: string) {
const greeting = this.helloService.greet(name);
return { greeting };
}
@Post('/greet')
async greetWithBody(@Body(greetBodySchema) body: GreetBody) {
const greeting = this.helloService.greet(body.name);
return {
greeting,
customMessage: body.message,
};
}
}Key Points:
@Controller(path)defines base path for all routesBaseControllerprovidesthis.logger,this.config, and response helpers@Param('name')extracts path parameters@Body(schema)validates and injects request body- Route declaration order does not matter — Bun's router resolves by specificity (static > parametric > wildcard)
- Constructor DI is automatic (just declare private property) — see Architecture — DI for details
Step 7: Create the Module
src/app.module.ts:
import { Module } from '@onebun/core';
import { HelloController } from './hello.controller';
import { HelloService } from './hello.service';
@Module({
controllers: [HelloController],
providers: [HelloService],
})
export class AppModule {}Module Structure:
controllers: Array of controller classesproviders: Array of service classesimports: Array of other modules to importexports: Array of services to export to parent modules
Step 8: Create Entry Point
src/index.ts:
import { OneBunApplication } from '@onebun/core';
import { AppModule } from './app.module';
import { envSchema } from './config';
const app = new OneBunApplication(AppModule, {
// port and host can be omitted - they'll use PORT/HOST env vars or defaults (3000/'0.0.0.0')
// port: 3000,
// host: '0.0.0.0',
development: true,
envSchema,
envOptions: {
loadDotEnv: true,
},
metrics: {
enabled: true,
path: '/metrics',
collectHttpMetrics: true,
collectSystemMetrics: true,
},
tracing: {
enabled: true,
serviceName: 'my-onebun-app',
traceHttpRequests: true,
},
});
app
.start()
.then(() => {
const logger = app.getLogger({ className: 'AppBootstrap' });
logger.info('Application started successfully');
})
.catch((error: unknown) => {
const logger = app.getLogger({ className: 'AppBootstrap' });
logger.error(
'Failed to start application:',
error instanceof Error ? error : new Error(String(error)),
);
process.exit(1);
});Step 9: Create .env File (Optional)
.env:
PORT=3000
HOST=0.0.0.0
APP_NAME=my-onebun-app
DEBUG=trueStep 10: Add Scripts to package.json
{
"scripts": {
"dev": "bun run --watch src/index.ts",
"dev:once": "bun run src/index.ts",
"test": "bun test",
"typecheck": "bunx tsc --noEmit"
}
}The CLI template ships only dev and start, so add dev:once, test and typecheck yourself if you scaffolded with bun create @onebun.
Step 11: Run the Application
# Start in development mode with hot reload
bun run dev
# Or start once
bun run dev:onceTest Your API
# Basic hello
curl http://localhost:3000/api/hello
# Greet by name
curl http://localhost:3000/api/hello/World
# Greet with body
curl -X POST http://localhost:3000/api/hello/greet \
-H "Content-Type: application/json" \
-d '{"name": "Alice", "message": "Welcome!"}'
# Get stats
curl http://localhost:3000/api/hello/stats
# Get metrics (Prometheus format)
curl http://localhost:3000/metricsExpected Responses
// GET /api/hello
{
"success": true,
"result": { "message": "Hello from OneBun!" }
}
// GET /api/hello/World
{
"success": true,
"result": { "greeting": "Hello, World! You are visitor #1" }
}
// POST /api/hello/greet
{
"success": true,
"result": {
"greeting": "Hello, Alice! You are visitor #2",
"customMessage": "Welcome!"
}
}Project Structure Summary
The layout you reach at the end of this guide (the CLI template starts from a smaller one — see Step 1):
my-onebun-app/
├── src/
│ ├── index.ts # Application entry point
│ ├── app.module.ts # Root module
│ ├── config.ts # Environment schema
│ ├── hello.schema.ts # Validation schemas
│ ├── hello.controller.ts
│ └── hello.service.ts
├── .env # Environment variables
├── package.json
└── tsconfig.jsonWhat's Next
You've built a basic OneBun application. Here's what else the framework offers:
Add Features to Your App
- Validation — ArkType schemas: one definition = TS types + runtime validation + OpenAPI 3.1 docs
- Database — Drizzle ORM with PostgreSQL/SQLite, schema-first types, auto-migrations
- Caching — In-memory and Redis with DI integration
- Queue & Scheduler — Background jobs with in-memory, Redis, NATS, JetStream backends
- WebSocket — Real-time communication with Socket.IO support and typed clients
Production Readiness
- Metrics — Prometheus-compatible: auto HTTP/system metrics, @Timed/@Counted/@Gauged decorators
- Tracing — OpenTelemetry with @Span decorator, trace context in logs
- HTTP Client — Typed clients with retries, auth schemes, inter-service HMAC
Scale to Microservices
- Multi-Service — Run multiple services from one codebase with OneBunApplication
- OpenAPI Docs — Auto-generated API documentation from schemas
Complete Examples
- CRUD API — Full CRUD with validation, error handling, repository pattern
- WebSocket Chat — Real-time chat application
- Multi-Service — Microservices with inter-service communication
Common Issues
Decorator Errors
Ensure experimentalDecorators and emitDecoratorMetadata are true in tsconfig.json.
Service Not Found
- Check that service has
@Service()decorator - Ensure service is listed in module's
providersarray - Service class must have
@Service()decorator for DI to work (enables TypeScript metadata emission)
Type Errors
Run bunx tsc --noEmit to check TypeScript errors before starting the app.
Backend Unavailable at Startup
A cache or database the application configures explicitly is a hard boot dependency: app.start() rejects and the HTTP server never binds. See Cache — Unreachable Redis at startup and Drizzle — Startup Contract for the failure messages and the allowDegradedStart opt-out.
