Skip to content

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:

bash
bun create @onebun my-onebun-app
cd my-onebun-app
bun run dev

This creates a ready-to-run project with all files from this guide. If you prefer to set up manually:

bash
mkdir my-onebun-app
cd my-onebun-app
bun init -y
bun add @onebun/core

All 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:

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:

typescript
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:

typescript
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 DI
  • BaseService provides this.logger and this.config
  • Constructor receives dependencies automatically

Step 5: Create Validation Schema

src/hello.schema.ts:

typescript
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:

typescript
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 routes
  • BaseController provides this.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:

typescript
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 classes
  • providers: Array of service classes
  • imports: Array of other modules to import
  • exports: Array of services to export to parent modules

Step 8: Create Entry Point

src/index.ts:

typescript
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:

bash
PORT=3000
HOST=0.0.0.0
APP_NAME=my-onebun-app
DEBUG=true

Step 10: Add Scripts to package.json

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

bash
# Start in development mode with hot reload
bun run dev

# Or start once
bun run dev:once

Test Your API

bash
# 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/metrics

Expected Responses

json
// 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.json

What'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 providers array
  • 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.

Released under the MPL-2.0 License.