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 project with all files from this guide. If you prefer to set up manually:
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"
}
}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
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.
