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

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

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

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

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

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.

Released under the MPL-2.0 License.