Skip to content

OneBun FrameworkA complete, batteries-included TypeScript backend framework for Bun.js

One framework. One runtime. Everything you need for production backend services.

OneBun Framework

A complete, batteries-included TypeScript backend framework for Bun.js.

OneBun brings NestJS-style architecture โ€” modules, dependency injection, decorators โ€” to the Bun.js runtime, with a full ecosystem of built-in packages: WebSocket (+ Socket.IO + typed client), microservices with single-image deployment, database integration (Drizzle ORM), message queues (Redis, NATS, JetStream), caching, Prometheus metrics, OpenTelemetry tracing, ArkType validation with auto-generated OpenAPI documentation, and typed inter-service HTTP clients.

One framework. One runtime. Everything you need for production backend services.

Quick Reference โ€‹

AspectDetails
RuntimeBun.js (not Node.js compatible)
LanguageTypeScript (strict mode, no any)
ArchitectureNestJS-inspired with Effect.ts for side effects
PhilosophyOne default solution per problem
API StylePromise-based for client code, Effect.js internally

Core Concepts โ€‹

Application Structure โ€‹

my-app/
โ”œโ”€โ”€ src/
โ”‚   โ”œโ”€โ”€ index.ts              # Entry point - creates OneBunApplication
โ”‚   โ”œโ”€โ”€ app.module.ts         # Root module
โ”‚   โ”œโ”€โ”€ config.ts             # Environment schema
โ”‚   โ”œโ”€โ”€ users/
โ”‚   โ”‚   โ”œโ”€โ”€ users.module.ts   # Feature module
โ”‚   โ”‚   โ”œโ”€โ”€ users.controller.ts
โ”‚   โ”‚   โ”œโ”€โ”€ users.service.ts
โ”‚   โ”‚   โ””โ”€โ”€ entities/
โ”‚   โ”‚       โ””โ”€โ”€ user.entity.ts
โ”‚   โ””โ”€โ”€ posts/
โ”‚       โ””โ”€โ”€ ...
โ”œโ”€โ”€ package.json
โ””โ”€โ”€ tsconfig.json

Decorator System โ€‹

DecoratorPurposeExample
@Module()Define module with imports, controllers, providers@Module({ controllers: [UserController] })
@Controller(path)Define HTTP controller@Controller('/users')
@Service()Mark class as injectable service@Service()
@Get(), @Post(), etc.Define HTTP endpoints@Get('/:id')
@Body(), @Param(), @Query()Parameter injection@Param('id') id: string
@Inject()Explicit DI โ€” rarely needed; prefer constructor params@Inject(AbstractService)
@WebSocketGateway()Define WebSocket gateway@WebSocketGateway({ path: '/ws' })
@OnMessage(), @OnConnect(), etc.WebSocket event handlers@OnMessage('chat:*')

Base Classes โ€‹

ClassPurposeInherit When
BaseControllerHTTP controller functionalityCreating any controller
BaseServiceService with logger and configCreating any service
BaseWebSocketGatewayWebSocket gateway with rooms and emitCreating WebSocket gateway

Minimal Working Example โ€‹

typescript
import {
  BaseController,
  BaseService,
  Controller,
  Env,
  Get,
  Module,
  OneBunApplication,
  Post,
  Body,
  Service,
  type InferConfigType,
} from '@onebun/core';

// ============================================================================
// 1. Environment Schema (src/config.ts)
// ============================================================================
const envSchema = {
  server: {
    port: Env.number({ default: 3000 }),
    host: Env.string({ default: '0.0.0.0' }),
  },
};

type AppConfig = InferConfigType<typeof envSchema>;

declare module '@onebun/core' {
  interface OneBunAppConfig extends AppConfig {}
}

// ============================================================================
// 2. Service Layer (src/counter.service.ts)
// ============================================================================
@Service()
class CounterService extends BaseService {
  private value = 0;

  getValue(): number {
    this.logger.debug('Getting counter value', { value: this.value });
    return this.value;
  }

  increment(amount = 1): number {
    this.value += amount;
    return this.value;
  }
}

// ============================================================================
// 3. Controller Layer (src/counter.controller.ts)
// ============================================================================
@Controller('/api/counter')
class CounterController extends BaseController {
  constructor(private counterService: CounterService) {
    super();
  }

  @Get('/')
  async getValue() {
    const value = this.counterService.getValue();
    return { value };
  }

  @Post('/increment')
  async increment(@Body() body?: { amount?: number }) {
    const newValue = this.counterService.increment(body?.amount);
    return { value: newValue };
  }
}

// ============================================================================
// 4. Module Definition (src/app.module.ts)
// ============================================================================
@Module({
  controllers: [CounterController],
  providers: [CounterService],
})
class AppModule {}

// ============================================================================
// 5. Application Entry Point (src/index.ts)
// ============================================================================
const app = new OneBunApplication(AppModule, {
  envSchema,
  metrics: { enabled: true },
  tracing: { enabled: true },
});

app
  .start()
  .then(() => {
    const logger = app.getLogger({ className: 'AppBootstrap' });
    logger.info('Application started');
  })
  .catch((error: unknown) => {
    const logger = app.getLogger({ className: 'AppBootstrap' });
    logger.error('Failed to start:', error instanceof Error ? error : new Error(String(error)));
  });

Key Patterns โ€‹

Response Format โ€‹

Controllers return plain data (auto-wrapped) and throw for errors:

typescript
// Success: return plain data (auto-wrapped)
return data;                              // โ†’ { success: true, result: data }

// Error: throw HttpException
throw new HttpException(400, 'message');  // โ†’ { success: false, error: 'message', code: 400 }

Dependency Injection โ€‹

Dependencies are auto-detected from constructor:

typescript
@Controller('/users')
export class UserController extends BaseController {
  // CounterService is automatically injected
  constructor(private counterService: CounterService) {
    super();
  }
}

Validation with ArkType โ€‹

typescript
// src/user.schema.ts โ€” schema + inferred type in one file
import { type } from '@onebun/core';

export const createUserSchema = type({
  name: 'string',
  email: 'string.email',
  age: 'number > 0',
});

export type CreateUserDto = typeof createUserSchema.infer;
typescript
// In controller โ€” import named type, don't use typeof inline
import { createUserSchema, type CreateUserDto } from './user.schema';

@Post('/')
async createUser(@Body(createUserSchema) user: CreateUserDto) {
  // user is validated and typed
}

Commands โ€‹

bash
# Development
bun run dev           # Start with watch mode
bun run dev:once      # Start once

# Testing
bun test              # Run all tests

# Type checking
bunx tsc --noEmit     # Check TypeScript errors

# Linting
bun lint              # Check lint errors

Package Dependencies โ€‹

Only @onebun/core is required โ€” it includes logger, envs, requests, metrics, trace, effect, and arktype as transitive dependencies.

json
{
  "dependencies": {
    "@onebun/core": "^0.4.0"
  }
}

Add optional packages as needed:

json
{
  "@onebun/drizzle": "^0.4.0",
  "@onebun/cache": "^0.4.0",
  "@onebun/nats": "^0.4.0",
  "@onebun/docs": "^0.4.0"
}

Released under the MPL-2.0 License.