Getting Started#

A comprehensive guide to get started building web applications with the Buntok Framework.

Prerequisites#

Before getting started, make sure you have installed:

  • Bun >= 1.2.0 - https://bun.sh
  • TypeScript >= 5.0
  • Node.js (optional, for npm package compatibility)
Info
OS Notes: Some features like enableReusePort() (SO_REUSEPORT) are Linux-only. On other platforms, this feature is disabled with a warning in development mode.

Quick Setup#

The fastest way to get started is by using buntok init:

typescript
# 1. Create a new project
mkdir my-app && cd my-app
bun init -y

# 2. Install buntok
bun add @buntok/core

# 3. Run the init command
bunx buntok init
Info
buntok init will automatically set up your project with an optimal configuration. You'll be asked if you want to deploy to Vercel - if yes, a vercel.json will be created for you.

Generated src/index.ts

Clean app setup - same for both Vercel and non-Vercel:

typescript
import { Buntok } from "@buntok/core";

export const app = new Buntok();

app.get("/", (ctx) => {
  return ctx.json({ message: "Hello from Buntok!" });
});

export default app;

Generated server.ts

Local server entry point used by the development script:

typescript
import { app } from "./src/index";
import { env } from "./src/env";

app.listen(env.PORT);

Generated src/env.ts

Type-safe environment schema with sensible defaults:

typescript
import { Buntok } from "@buntok/core";
import { z } from "@buntok/core/middlewares/validator";

export const env = Buntok.validateEnv({
  PORT: z.coerce.number().default(1212),
  AUTH_STORE: z.enum(["header", "cookie"]).default("header"),
  AUTH_COOKIE: z.string().default("session"),
  NODE_ENV: z.enum(["development", "production", "test"]).default("development"),
});

What does buntok init do?

This command will create and configure:

FileDescription
src/index.tsApplication entry point - exports the app instance
tsconfig.jsonTypeScript configuration
src/env.tsType-safe environment schema
biome.jsonBiome linter configuration
.envEnvironment variables
.env.exampleEnvironment variables template
.vscode/settings.jsonVS Code settings for this project
.gitignoreGit ignore rules
.agents/skills/buntok-skill/SKILL.mdSkill guide for AI assistants
vercel.json (optional)Vercel deployment config - includes framework: "bun"
server.tsLocal server entry point - calls app.listen(env.PORT)
Tip
.agents/skills/buntok-skill/SKILL.md contains the complete framework documentation that can be read by AI assistants such as Cursor, Copilot, or Claude.

Environment Configuration#

Buntok uses environment variables for configuration. The buntok init command creates a type-safe src/env.ts file and a .env file automatically.

Default Schema#

The generated src/env.ts validates your environment at startup with sensible defaults:

typescript
import { Buntok } from "@buntok/core";
import { z } from "@buntok/core/middlewares/validator";

export const env = Buntok.validateEnv({
  PORT: z.coerce.number().default(1212),
  AUTH_STORE: z.enum(["header", "cookie"]).default("header"),
  AUTH_COOKIE: z.string().default("session"),
  NODE_ENV: z.enum(["development", "production", "test"]).default("development"),
});

Adding More Variables#

Extend the schema as needed. The server will exit with a clear error if any required variable is missing or invalid:

typescript
export const env = Buntok.validateEnv({
  PORT: z.coerce.number().default(1212),
  DATABASE_URL: z.string().url(),
  JWT_SECRET: z.string().min(32),
  NODE_ENV: z.enum(["development", "production", "test"]).default("development"),
});
Warning
If validation fails, the server prints a formatted error and calls process.exit(1). The app will not start.
Info
Learn more in Buntok Configuration.

Project Structure#

Project structure after buntok init (with Vercel selected):

typescript
my-app/
├── src/
│   ├── index.ts              # export const app = new Buntok()
│   ├── env.ts                # Type-safe env schema
│   └── modules/              # Feature-based modules
│       └── user/
│           ├── index.ts      # barrel export
│           ├── user.schema.ts
│           ├── user.repository.ts
│           ├── user.service.ts
│           └── user.controller.ts
├── server.ts                 # app.listen(env.PORT) - local server entry point
├── vercel.json               # Vercel config (framework: "bun")
├── .agents/
│   └── skills/
│       └── buntok-skill/
│           └── SKILL.md      # AI skill guide
├── .vscode/
│   └── settings.json         # VS Code settings
├── .env                      # Environment variables
├── .env.example              # Environment variables template
├── .gitignore                # Git ignore rules
├── biome.json                # Linter config
├── tsconfig.json             # TypeScript config
└── package.json
Info
Without Vercel, vercel.json is not created. server.ts is always created for local development. A serverless deployment uses app.fetch through its deployment adapter.

Database Setup#

Buntok supports multiple database drivers through official packages:

PackageDescription
@buntok/prismaPrisma ORM integration
@buntok/drizzleDrizzle ORM integration
@buntok/typeormTypeORM integration

We recommend using Prisma v7 for better stability and performance with Bun. Prisma v7 includes native Bun support and improved query performance.

typescript
# Install Prisma v7 with Buntok integration
bun add @buntok/prisma prisma@7 @prisma/client@7

# Initialize Prisma schema
bunx prisma init

# Generate Prisma client
bunx prisma generate
Info
Why Prisma v7?
  • Native Bun runtime support (no Node.js polyfills needed)
  • Faster query performance with optimized SQL generation
  • Smaller bundle size compared to v6
  • Better TypeScript inference and type safety
typescript
// src/lib/prisma.ts
import { PrismaClient } from "@prisma/client";

export const prisma = new PrismaClient();
typescript
// src/repositories/user.repository.ts
import { BaseRepository } from "@buntok/prisma";
import { prisma } from "@/lib/prisma";
import type { User, PrismaClient, Prisma } from "@prisma/client";

export class UserRepository extends BaseRepository<
  User,
  PrismaClient,
  Prisma.UserCreateInput,
  Prisma.UserUpdateInput
> {
  constructor() {
    super(prisma, "user");
  }
}
typescript
// src/services/user.service.ts
import { BaseService } from "@buntok/core";
import { UserRepository } from "@/repositories/user.repository";
import type { User } from "@prisma/client";

export class UserService extends BaseService<User> {
  constructor(private userRepository: UserRepository) {
    super(userRepository);
  }
}
Info
Use buntok db migrate to run migrations and buntok db seed to seed your database.

Architecture Overview#

Buntok follows a layered architecture pattern for organized, maintainable code. Base classes auto-generate CRUD operations so you don't have to write boilerplate.

Request Flow#

typescript
Request
  │
  ▼
Route (decorator-based or manual)
  │
  ▼
Controller (handles HTTP, validates input)
  │
  ▼
Service (business logic)
  │
  ▼
Repository (data access)
  │
  ▼
Database

Layer Descriptions#

LayerBase ClassResponsibility
ControllerBaseControllerHandles HTTP requests/responses, auto-registers CRUD routes
ServiceBaseServiceBusiness logic, data transformation, NotFoundError handling
RepositoryBaseRepositoryData access layer, database queries, lifecycle hooks

Auto-Generated CRUD#

Extend the base classes to get a full CRUD API without writing boilerplate:

typescript
// 1. Repository - extends BaseRepository from your ORM
import { BaseRepository } from "@buntok/prisma"; // or @buntok/drizzle, @buntok/typeorm

export class UserRepository extends BaseRepository<User, PrismaClient> {
  constructor(prisma: PrismaClient) {
    super(prisma, "user");
  }
}

// 2. Service - extends BaseService
import { BaseService } from "@buntok/core";

export class UserService extends BaseService<User, CreateUser, UpdateUser> {
  constructor(private repo: UserRepository) {
    super(repo);
  }
}

// 3. Controller - extends BaseController (auto-registers 5 CRUD routes!)
import { Controller } from "@buntok/core";
import { BaseController } from "@buntok/core";

@Controller("/users")
export class UserController extends BaseController<User, CreateUser, UpdateUser> {
  constructor(private service: UserService) {
    super(service);
  }
}

// This automatically creates:
// GET    /       → service.getAll()
// GET    /:id    → service.getById(id)
// POST   /       → service.create(body)
// PUT    /:id    → service.update(id, body)
// DELETE /:id    → service.delete(id)
Info
Each ORM has its own BaseRepository: @buntok/prisma, @buntok/drizzle, or @buntok/typeorm. Choose the one that matches your stack.

Routing Approaches#

Buntok supports two routing approaches:

Routes are automatically registered using decorators:

typescript
import { Controller, Get, Post } from "@buntok/core";

@Controller("/users")
export class UserController {
  @Get("/")
  findAll(ctx) {
    return ctx.json({ users: [] });
  }

  @Post("/")
  create(ctx) {
    return ctx.json({ message: "User created" }, 201);
  }
}

2. Manual Routing (Optional)#

For simple cases, you can define routes directly on the app:

typescript
import { Buntok } from "@buntok/core";

const app = new Buntok();

app.get("/", (ctx) => {
  return ctx.json({ message: "Hello, Buntok!" });
});

app.post("/users", (ctx) => {
  return ctx.json({ message: "User created" }, 201);
});

CLI Commands#

Buntok provides several commands to help with development:

CommandDescription
buntok initSet up project with optimal configuration
buntok buildBuild project for production (output to buntok/)
buntok create <entity>Generate repository, service, and controller in src/modules/<entity>/
buntok db migrateRun pending database migrations
buntok db seedSeed database with initial data
buntok make:test <name>Generate unit test file
buntok make:test:e2e <name>Generate E2E test file
buntok make:middleware <name>Generate middleware file
buntok make:seeder <name>Generate database seeder
buntok make:docsManual OpenAPI docs regeneration (auto-generated on startup)

Code Generation Example#

Running buntok create user generates:

typescript
$ bunx buntok create user

Creating User entity (orm: prisma)...

✓ Repository: src/modules/user/user.repository.ts
✓ Service: src/modules/user/user.service.ts
✓ Controller: src/modules/user/user.controller.ts
✓ Schema: src/modules/user/user.schema.ts
✓ Barrel: src/modules/user/index.ts
typescript
// src/modules/user/user.controller.ts
import { Controller, Get, Post, Put, Delete } from "@buntok/core";
import { Dependencies } from "@buntok/core";
import { UserService } from "./user.service";

@Dependencies(UserService)
@Controller("/users")
export class UserController {
  constructor(private service: UserService) {}

  @Get("/")
  async findAll(ctx) {
    const users = await this.service.findAll();
    return ctx.json({ data: users });
  }

  @Get("/:id")
  async findById(ctx) {
    const user = await this.service.findById(ctx.params.id);
    if (!user) {
      return ctx.json({ message: "User not found" }, 404);
    }
    return ctx.json({ data: user });
  }

  @Post("/")
  async create(ctx) {
    const body = await ctx.body();
    const user = await this.service.create(body);
    return ctx.json({ data: user }, 201);
  }
}
Info
Use buntok create user --repo, --service, or --controller for partial generation. Add --prisma, --drizzle, or --typeorm to specify ORM.

Package Scripts#

buntok init will also add scripts to package.json:

typescript
{
  "scripts": {
    "dev": "bun --watch server.ts",
    "build": "bunx buntok build",
    "start": "bun buntok/server.js",
    "check": "bunx @biomejs/biome check --write .",
    "format": "bunx @biomejs/biome format --write .",
    "lint": "bunx @biomejs/biome lint ."
  }
}

Run the Server#

Development Mode#

typescript
# Hot reload with file watching
bun run dev

# Or directly
bun run server.ts

Production Mode#

typescript
# Build for production
bun run build

# Run the built output
bun run start

Deploy to Vercel#

typescript
# Install Vercel CLI
npm i -g vercel

# Deploy
vercel

# Production deploy
vercel --prod

Open http://localhost:1212 in your browser. You will see:

typescript
{ "message": "Hello, Buntok!" }

Port Auto-Increment#

If the specified port is already in use, Buntok will automatically try the next available port:

typescript
⚠ Port 3000 is already in use, using port 3001 instead
Server listening at http://localhost:3001
Info
This prevents crashes when running multiple instances or when another service is using the same port. The server will try up to 10 ports before failing.

Troubleshooting#

Port Already in Use#

If you see a port conflict warning, Buntok will automatically use the next available port. You can also specify a different port:

typescript
app.listen(3000); // Try port 3000
// Or via environment variable
PORT=3000 bun run dev

Error During bunx buntok init

If the init command fails, try these steps:

  • Make sure you're in the correct directory with a package.json
  • Ensure Bun is installed and up to date: bun --version
  • Check if you have write permissions in the current directory

Environment Variables Not Read#

If your environment variables aren't being read:

  • Make sure .env is in your project root (same level as package.json)
  • Use validateEnv() to catch missing/invalid variables early
  • Restart the server after changing .env files

Database Connection Refused#

If you can't connect to your database:

  • Verify DATABASE_URL is set correctly in .env
  • Ensure your database server is running
  • Run migrations: buntok db migrate
  • For Prisma, regenerate the client: bunx prisma generate

Vercel Build Error: `readFile` undefined#

If you see cannot read properties of undefined (reading 'readFile') on Vercel:

  • Remove typescript from peerDependencies in package.json
  • Keep it only as devDependency if needed
  • TypeScript is already included via tsup and other build tools
Info
See Vercel Deployment for more details.

Next Steps#