A modern TypeScript framework powered by Bun.
Bejibun is a high-performance framework built on the Bun runtime, following the proven Model–View–Controller (MVC) architecture. It provides a structured and scalable foundation for developing modern web applications and APIs while maintaining a strong focus on developer productivity and code maintainability.
Designed for rapid application development, Bejibun includes a comprehensive set of built-in features such as database migrations, seeders, caching, CORS management, validation, and middleware support. These capabilities enable developers to build production-ready applications with minimal configuration.
The framework features a powerful Command Line Interface (CLI) that automates common development tasks through code generation and scaffolding tools. Developers can quickly generate controllers, models, middleware, migrations, seeders, validators, and other application components, significantly reducing boilerplate code and accelerating development workflows.
Bejibun also includes application lifecycle management tools, including maintenance mode support, allowing applications to be seamlessly transitioned between live and maintenance states during deployments and system updates.
By combining the speed of Bun with a clean MVC architecture and a robust developer experience, Bejibun empowers teams to build scalable, maintainable, and efficient backend services with confidence.
Documentation : bejibun.com
X (Twitter) : @bjbnframework
Contract Address : CQhbNnCGKfDaKXt8uE61i5DrBYJV7NPsCDD9vQgypump
- Bun — JavaScript and TypeScript Runtime
- Knex.js — Database Migrations and Seeders
- Objection.js — ORM and Model Layer
- Vine.js — Request Validation
- Luxon — Date and Time Management
- React — Project Website and Documentation Portal
- MVC Architecture
- TypeScript First
- Built on Bun Runtime
- Database Migrations & Seeders
- Model ORM Integration
- Validation System
- Middleware Support
- CORS Management
- Storage Management
- Caching System
- CLI Scaffolding Tools
- Maintenance Mode
- X402 Protocol
- Production-Ready Structure
- Scalable and Maintainable Codebase
If you don't have bun installed :
# Linux / Mac OS
curl -fsSL https://bun.sh/install | bash
# Windows
powershell -c "irm bun.sh/install.ps1 | iex"Setup the project.
bunx @bejibun/cli your-projectTo see list of available commands, run.
bun ace
bun ace help
bun ace --h
bun ace --helpTo see help of specific command, run :
bun ace help migrate:latest
bun ace migrate:latest --h
bun ace migrate:latest --helpTo fresh or drop all table and re-run the migrations, run :
bun ace migrate:freshExample :
This will DROP ALL tables and re-run ALL migrations. Are you want to continue? (Y/N): Y
✔ Rolled back all migrations
✔ Batch 1 finished
✔ 20250929_000001_tests.tsTo migrate the migrations, run :
bun ace migrate:latestExample :
✔ Batch 1 finished
✔ 20250929_000001_tests.tsTo rollback the migrations, run :
bun ace migrate:rollbackExample :
This will ROLLBACK latest migrations. Are you want to continue? (Y/N): Y
✔ Batch 1 finished
✔ 20250929_000001_tests.tsTo see migrations status, run :
bun ace migrate:statusExample :
✔ Completed Migrations :
✔ No migrations were completed.
✔ Pending Migrations :
✔ 20250929_000001_tests.tsTo execute seeder, run :
bun ace db:seedExample :
✔ Seeding finished
✔ 20250929_000001_seeder_test.tsTo run the project, run :
# Development Mode
bun dev
# Production Mode
bun startLogical processes
Example :
import BaseController from "@bejibun/core/bases/BaseController";
export default class HelloController extends BaseController {
public async hello(request: Bejibun.Request): Promise<Response> {
return super.response
.setData({
message: "Hello, world!",
method: request.method
})
.send();
}
}Handle any incoming errors
Example :
import ExceptionHandler from "@bejibun/core/exceptions/ExceptionHandler";
export default class Handler extends ExceptionHandler {
public handle(error: any): Bejibun.Response {
// Your code goes here
return super.handle(error);
}
}Handle any request before forwarding to controller
Example :
import type {HandlerType} from "@bejibun/core/types";
import Logger from "@bejibun/logger";
export default class TestMiddleware {
public handle(handler: HandlerType): HandlerType {
return async (request: Bejibun.Request) => {
Logger.setContext("TestMiddleware").debug(request.url);
return handler(request);
};
}
}Usage :
import Router from "@bejibun/core/facades/Router";
import YourController from "@/app/controllers/YourController";
import TestMiddleware from "@/app/middlewares/TestMiddleware";
import LoggerMiddleware from "@/app/middlewares/LoggerMiddleware";
export default Router.prefix("test")
.middleware(new TestMiddleware(), new LoggerMiddleware())
.group([
Router.get("/", "TestController@index"),
Router.get("/:id", "TestController@show"),
Router.post("/", "TestController@store"),
Router.put("/:id", "TestController@update"),
Router.delete("/:id", "TestController@destroy"),
Router.patch("/:id", "TestController@restore"),
Router.resource("path", YourController),
Router.resource("path", YourController, {
only: ["index", "store"] // "index" | "store" | "show" | "update" | "destroy"
}),
Router.resource("path", YourController, {
except: ["index", "store"] // "index" | "store" | "show" | "update" | "destroy"
})
]);Validate any incoming requests
Example :
import BaseValidator from "@bejibun/core/bases/BaseValidator";
import TestModel from "@/app/models/TestModel";
export default class TestValidator extends BaseValidator {
public static get show(): Bejibun.Validator {
return super.validator.create({
id: super.validator.number().min(1).exists(TestModel, "id")
});
}
public static get store(): Bejibun.Validator {
return super.validator.create({
name: super.validator.string()
});
}
public static get update(): Bejibun.Validator {
return super.validator.create({
id: super.validator.number().min(1).exists(TestModel, "id"),
name: super.validator.string()
});
}
public static get destroy(): Bejibun.Validator {
return super.validator.create({
id: super.validator.number().min(1).exists(TestModel, "id")
});
}
public static get restore(): Bejibun.Validator {
return super.validator.create({
id: super.validator.number().min(1).exists(TestModel, "id", true)
});
}
}Usage :
import BaseController from "@bejibun/core/bases/BaseController";
import TestModel from "@/app/models/TestModel";
import TestValidator from "@/app/validators/TestValidator";
export default class TestController extends BaseController {
@ApiDoc({
description: "Show detail test",
tags: ["Test"],
request: {
params: [
{
name: "id",
in: "path",
required: true,
schema: {
type: "number"
}
}
]
}
})
public async show(request: Bejibun.Request): Promise<Response> {
await request.validate(TestValidator.show);
const test = await TestModel.findOrFail(request.integer("id"));
return super.response.setData(test).send();
}
}Database table model
Example :
import type {Timestamp, NullableTimestamp} from "@bejibun/core/bases/BaseModel";
import BaseModel from "@bejibun/core/bases/BaseModel";
export default class TestModel extends BaseModel {
public static tableName: string = "tests";
public static idColumn: string = "id";
declare id: bigint;
declare name: string;
declare created_at: Timestamp;
declare updated_at: Timestamp;
declare deleted_at: NullableTimestamp;
}Example :
import BaseController from "@bejibun/core/bases/BaseController";
import TestModel from "@/app/models/TestModel";
export default class TestController extends BaseController {
@ApiDoc({
description: "Get test list",
tags: ["Test"]
})
public async index(request: Bejibun.Request): Promise<Response> {
const tests = await TestModel.all();
return super.response.setData(tests).send();
}
}Example :
import BaseController from "@bejibun/core/bases/BaseController";
import TestModel from "@/app/models/TestModel";
import TestValidator from "@/app/validators/TestValidator";
export default class TestController extends BaseController {
@ApiDoc({
description: "Show detail test",
tags: ["Test"],
request: {
params: [
{
name: "id",
in: "path",
required: true,
schema: {
type: "number"
}
}
]
}
})
public async show(request: Bejibun.Request): Promise<Response> {
await request.validate(TestValidator.show);
const test = await TestModel.findOrFail(request.integer("id"));
return super.response.setData(test).send();
}
}Example :
import BaseController from "@bejibun/core/bases/BaseController";
import TestModel from "@/app/models/TestModel";
import TestValidator from "@/app/validators/TestValidator";
export default class TestController extends BaseController {
@ApiDoc({
description: "Store test data",
tags: ["Test"],
request: {
params: [
{
name: "name",
in: "query",
required: true,
schema: {
type: "string"
}
}
]
}
})
public async store(request: Bejibun.Request): Promise<Response> {
await request.validate(TestValidator.store);
const test = await TestModel.create({
name: request.get("name") as string
});
return super.response.setData(test).send();
}
}Example :
import BaseController from "@bejibun/core/bases/BaseController";
import TestModel from "@/app/models/TestModel";
import TestValidator from "@/app/validators/TestValidator";
export default class TestController extends BaseController {
@ApiDoc({
description: "Update test data",
tags: ["Test"],
request: {
params: [
{
name: "id",
in: "path",
required: true,
schema: {
type: "number"
}
},
{
name: "name",
in: "path",
required: true,
schema: {
type: "string"
}
}
]
}
})
public async update(request: Bejibun.Request): Promise<Response> {
await request.validate(TestValidator.update);
const test = await TestModel.find(request.integer("id")).update({
name: request.get("name") as string
});
return super.response.setData(test).send();
}
}Example :
import BaseController from "@bejibun/core/bases/BaseController";
import TestModel from "@/app/models/TestModel";
import TestValidator from "@/app/validators/TestValidator";
export default class TestController extends BaseController {
@ApiDoc({
description: "Destroy test data",
tags: ["Test"],
request: {
params: [
{
name: "id",
in: "path",
required: true,
schema: {
type: "number"
}
}
]
}
})
public async destroy(request: Bejibun.Request): Promise<Response> {
await request.validate(TestValidator.destroy);
const test = await TestModel.find(request.integer("id")).delete();
return super.response.setData(test).send();
}
}Example :
import BaseController from "@bejibun/core/bases/BaseController";
import TestModel from "@/app/models/TestModel";
import TestValidator from "@/app/validators/TestValidator";
export default class TestController extends BaseController {
@ApiDoc({
description: "Destroy test data",
tags: ["Test"],
request: {
params: [
{
name: "id",
in: "path",
required: true,
schema: {
type: "number"
}
}
]
}
})
public async destroy(request: Bejibun.Request): Promise<Response> {
await request.validate(TestValidator.destroy);
const test = await TestModel.find(request.integer("id")).forceDelete();
return super.response.setData(test).send();
}
}Example :
import BaseController from "@bejibun/core/bases/BaseController";
import TestModel from "@/app/models/TestModel";
export default class TestController extends BaseController {
@ApiDoc({
description: "Get test list",
tags: ["Test"]
})
public async indexWithTrashed(request: Bejibun.Request): Promise<Response> {
const tests = await TestModel.withTrashed();
return super.response.setData(tests).send();
}
}Example :
import BaseController from "@bejibun/core/bases/BaseController";
import TestModel from "@/app/models/TestModel";
export default class TestController extends BaseController {
@ApiDoc({
description: "Get test list",
tags: ["Test"]
})
public async indexOnlyTrashed(request: Bejibun.Request): Promise<Response> {
const tests = await TestModel.onlyTrashed();
return super.response.setData(tests).send();
}
}Example :
import BaseController from "@bejibun/core/bases/BaseController";
import TestModel from "@/app/models/TestModel";
export default class TestController extends BaseController {
@ApiDoc({
description: "Restore test data",
tags: ["Test"],
request: {
params: [
{
name: "id",
in: "path",
required: true,
schema: {
type: "number"
}
}
]
}
})
public async restore(request: Bejibun.Request): Promise<Response> {
await request.validate(TestValidator.restore);
const test = await TestModel.find(request.integer("id")).restore();
return super.response.setData(test).send();
}
}Example :
import type {Knex} from "knex";
import TestModel from "@/app/models/TestModel";
export function up(knex: Knex): void {
return knex.schema.createTable(TestModel.table, (table: Knex.TableBuilder) => {
table.bigIncrements("id");
table.string("name");
table.timestamps(true, true);
table.timestamp("deleted_at");
});
}
export function down(knex: Knex): void {
return knex.schema.dropTable(TestModel.table);
}Example :
import type {Knex} from "knex";
import TestModel from "@/app/models/TestModel";
export async function seed(knex: Knex): Promise<void> {
for (const name of ["Name 1", "Name 2", "Name 3"]) {
await TestModel.query(knex).insert({
name: name
});
}
}For public assets and Frontend use.
Currently, used for frontend initiation.
Any startup loads
Currently, only load from core package.
Example :
import "@bejibun/core/bootstrap";Documentation: @bejibun/storage
A filesystem facade, with built-in disk management including disks configuration and build disk at runtime.
- Standard Use
import Storage from "@bejibun/storage";
await Storage.exists("path/to/your/file.ext"); // Check if the file exists
await Storage.missing("path/to/your/file.ext"); // Check if the file doesn't exists
await Storage.get("path/to/your/file.ext"); // Get data content
await Storage.put("path/to/your/file.ext", "content"); // Store content to file
await Storage.copy("source/file.ext", "destination/file.ext"); // Copy file
await Storage.move("source/file.ext", "destination/file.ext"); // Move file
await Storage.delete("path/to/your/file.ext"); // Delete file
await Storage.metadata("path/to/your/file.ext"); // Retrieve complete file metadata and statistics
await Storage.size("path/to/your/file.ext"); // Get the file size in bytes
await Storage.mimeType("path/to/your/file.ext"); // Get the file MIME type
await Storage.lastModified("path/to/your/file.ext"); // Get the file's last modification date- With Specified Disk
import Storage from "@bejibun/storage";
await Storage.disk("public").exists("path/to/your/file.ext");
await Storage.disk("public").missing("path/to/your/file.ext");
await Storage.disk("public").get("path/to/your/file.ext");
await Storage.disk("public").put("path/to/your/file.ext", "content");
await Storage.disk("public").copy("source/file.ext", "destination/file.ext");
await Storage.disk("public").move("source/file.ext", "destination/file.ext");
await Storage.disk("public").delete("path/to/your/file.ext");
await Storage.disk("public").metadata("path/to/your/file.ext");
await Storage.disk("public").size("path/to/your/file.ext");
await Storage.disk("public").mimeType("path/to/your/file.ext");
await Storage.disk("public").lastModified("path/to/your/file.ext");- New Disk at Runtime
import Storage from "@bejibun/storage";
await Storage.build({
driver: "local", // "local" | StorageDiskDriverEnum.Local
root: App.Path.storagePath("custom")
}).exists("path/to/your/file.ext");
await Storage.build({
driver: "local",
root: App.Path.storagePath("custom")
}).missing("path/to/your/file.ext");
await Storage.build({
driver: "local",
root: App.Path.storagePath("custom")
}).get("path/to/your/file.ext");
await Storage.build({
driver: "local",
root: App.Path.storagePath("custom")
}).put("path/to/your/file.ext", "content");
await Storage.build({
driver: "local",
root: App.Path.storagePath("custom")
}).copy("source/file.ext", "destination/file.ext");
await Storage.build({
driver: "local",
root: App.Path.storagePath("custom")
}).move("source/file.ext", "destination/file.ext");
await Storage.build({
driver: "local",
root: App.Path.storagePath("custom")
}).delete("path/to/your/file.ext");
await Storage.build({
driver: "local",
root: App.Path.storagePath("custom")
}).metadata("path/to/your/file.ext");
await Storage.build({
driver: "local",
root: App.Path.storagePath("custom")
}).size("path/to/your/file.ext");
await Storage.build({
driver: "local",
root: App.Path.storagePath("custom")
}).mimeType("path/to/your/file.ext");
await Storage.build({
driver: "local",
root: App.Path.storagePath("custom")
}).lastModified("path/to/your/file.ext");Documentation: @bejibun/limiter
Throttle repeated actions -- login attempts, API calls, anything you need to cap -- with a simple key-based counter.
import RateLimiter from "@bejibun/limiter";
await RateLimiter.attempt(
`user:${user.id}`,
60 /* limit */,
() => {
//
},
60 /* duration (optional) */
);
await RateLimiter.tooManyAttempts(`user:${user.id}`, 60 /* limit */, 60 /* duration (optional) */);
await RateLimiter.clear(`user:${user.id}`);Run processes at background.
// Immediately
await TestJob.dispatch(/*any params here*/).send();
// With delay
await TestJob.dispatch(/*any params here*/)
.delay(60 * 10 /*10 minutes*/)
.send();All available decorators.
@ApiDoc({
description: "Hello with Name",
request: {
params: [
{
name: "name",
in: "path",
required: true,
schema: {
type: "string"
}
}
]
}
})
public async helloName(request: Bejibun.Request): Promise<Response> {
await request.validate(HelloValidator.helloName);
return super.response
.setData({
message: `Hello, ${request.get("name")}!`
})
.send();
}Run code in period.
import type Schedule from "@bejibun/core/facades/Schedule";
export default class Kernel {
public schedule(schedule: Schedule): void {
// Your code goes here
schedule.command("hello:world").everyMinute();
}
}Setup websocket like router.
import Router from "@bejibun/core/facades/Router";
export default Router.prefix("chat").group([Router.websocket("/", "ChatWebSocket@handle")]);import BaseWebSocket from "@bejibun/core/bases/BaseWebSocket";
export default class ChatWebSocket extends BaseWebSocket {
public async handle(
ws: Bun.ServerWebSocket<any>,
message: string | Buffer<ArrayBuffer>
): Promise<void> {
for (const connection of super.connections) {
if (connection.data.id !== ws.data.id) {
if (connection.readyState === 1) {
connection.send(message);
}
}
}
}
}config("disk.default");env("APP_KEY");Documentation: @bejibun/redis
import type {RedisPipeline} from "@bejibun/redis/types";
import BaseController from "@bejibun/core/bases/BaseController";
import Logger from "@bejibun/logger";
import Redis from "@bejibun/redis";
export default class TestController extends BaseController {
public async redis(request: Bejibun.Request): Promise<Response> {
await Redis.set("redis", {hello: "world"});
const keys = await Redis.keys("pattern");
const redis = await Redis.get("redis");
await Redis.connection("local").set("connection", "This is using custom connection.");
const connection = await Redis.connection("local").get("connection");
await Redis.setClient(
{
host: "127.0.0.1",
port: 6379,
password: "",
database: 0,
maxRetries: 10
},
"optional-connection-name"
).set("redis", {hello: "world"});
// for publish and subscibe recommended using custom connection name to make sure connection matched
const pipeline = await Redis.pipeline((pipe: RedisPipeline) => {
pipe.set("redis-pipeline-1", "This is redis pipeline 1");
pipe.set("redis-pipeline-2", "This is redis pipeline 2");
pipe.get("redis-pipeline-1");
pipe.get("redis-pipeline-2");
});
const subscriber = await Redis.subscribe(
"redis-subscribe",
(message: string, channel: string) => {
Logger.setContext(channel).debug(message);
}
);
await Redis.publish("redis-subscribe", "Hai redis subscriber!");
await Bun.sleep(500);
await subscriber.unsubscribe();
await Redis.exists("visitors");
await Redis.incr("visitors");
await Redis.decr("visitors");
await Redis.incrBy("visitors", 10);
await Redis.decrBy("visitors", 5);
return super.response.setData({redis, connection, pipeline}).send();
}
}Documentation: @bejibun/cors
const config: Record<string, any> = {
allowedHeaders: "*",
credentials: false,
exposedHeaders: [],
maxAge: 86400,
methods: "*",
origin: "*"
};
export default config;Documentation: @bejibun/cache
import Cache from "@bejibun/cache";
Cache.connection();
await Cache.remember("key", () => {}, 60 /* seconds */); // any
await Cache.has("key"); // boolean
await Cache.get("key"); // any
await Cache.add("key", "Hello world", 60 /* seconds */); // boolean
await Cache.put("key", "Lorem ipsum", 60 /* seconds */); // boolean
await Cache.forget("key"); // void
await Cache.increment("key"); // number
await Cache.decrement("key"); // number
await Cache.incrementBy("key", 5); // number
await Cache.decrementBy("key", 5); // numberAny commands for development
Usage: ace [options] [command]
Ace for your commander
Author: Havea Crenata <havea.crenata@gmail.com>
Options:
-v, --version Show the current version
-h, --help display help for command
Commands:
db:seed [options] Run database seeders
hello:world Run hello world
install <packages...> Install package dependencies
maintenance:down [options] Turn app into maintenance mode
maintenance:up Turn app into live mode
make:command <file> Create a new command file
make:controller <file> Create a new controller file
make:job <file> Create a new job file
make:middleware <file> Create a new middleware file
make:migration <file> Create a new migration file
make:model <file> Create a new model file
make:seeder <file> Create a new seeder file
make:validator <file> Create a new validator file
migrate:fresh [options] Rollback all migrations and re-run migrations
migrate:latest Run latest migration
migrate:rollback [options] Rollback the latest migrations
migrate:status [options] List migrations status
package:configure [options] Configure package after installation
queue:flush Flush all of the failed queue jobs
queue:retry Retry a failed queue job
queue:work Start processing jobs on the queue as a daemon
route:list List all registered routes
schedule:work Start the schedule worker
help [command] display help for command
Examples:
$ bun ace --help
$ bun ace --version
$ bun ace migrate:latestIf you find this project helpful and want to support it:
Or you can buy this $BJBN (Bejibun) tokens here.
