@danielgl/steampunk
v1.0.5
Published
A high-performance, DX-focused web framework for Bun, inspired by ASP.NET Core.
Maintainers
Readme
Features
- Dependency Injection: Powerful
ServiceCollectionandServiceProviderout of the box (Singleton,Scoped,Transient). - Builder Pattern: Familiar
WebApplication.createBuilder()approach. - Decorators: Beautiful Controller and Routing decorators (
@ApiController(),@Get(),@Post(),@FromRoute(), etc). - Global Error Handling: Built-in error catching at the controller level with customizable hooks.
- Built-in Logging: Colorized console output for better observability out of the box.
- Extremely Fast: Built natively on top of
Bun.serve().
Installation
bun add @danielgl/steampunk reflect-metadataEnsure your tsconfig.json has experimentalDecorators and emitDecoratorMetadata enabled:
{
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
}Quick Start
1. Create a Service
import { Injectable } from "@danielgl/steampunk";
export interface User {
id: string;
name: string;
}
@Injectable()
export class UsersService {
private users: User[] = [{ id: "1", name: "Alice" }];
public getAll() {
return this.users;
}
public getById(id: string) {
return this.users.find((u) => u.id === id);
}
}2. Create a Controller
import { ApiController, Controller, Get, FromRoute, HttpResult } from "@danielgl/steampunk";
import { UsersService } from "./users-service";
@ApiController("users")
export class UsersController extends Controller {
// Services are automatically injected!
constructor(private usersService: UsersService) {
super();
}
@Get()
public getAllUsers(): HttpResult {
const users = this.usersService.getAll();
return HttpResult.ok(users);
}
@Get(":id")
public getUserById(@FromRoute("id") id: string): HttpResult {
const user = this.usersService.getById(id);
if (!user) {
return HttpResult.notFound("User not found");
}
return HttpResult.ok(user);
}
}3. Error Handling
Steampunk provides a global error handling mechanism. Every controller that extends the base Controller class can override the handleError method.
@ApiController("profile")
export class ProfileController extends Controller {
// Optional: custom error handling per controller
public override async handleError(error: unknown, context: HttpContext) {
// Log custom error or return specific HttpResult
return HttpResult.internalServerError("Custom error message");
}
@Get()
public getProfile() {
throw new Error("Something went wrong!"); // Automatically caught by handleError
}
}4. Bootstrap your App
import { WebApplication } from "@danielgl/steampunk";
import { UsersService } from "./users-service";
import { UsersController } from "./users-controller";
const builder = WebApplication.createBuilder();
// 1. Register DI Services
builder.services.addSingleton(UsersService);
// 2. Register Controllers (Explicit or Dynamic)
await builder.addControllers([UsersController]);
const app = builder.build();
// 3. Run!
app.run(8080);Production & Compilation
When using bun build --compile to create a single-binary application, dynamic filesystem discovery via builder.addControllers() without arguments may fail because the source directory is not present at runtime.
For production or compiled binaries, explicit registration via the builder is recommended:
import { UsersController } from "./controllers/users-controller";
// Best for compilation/bundling
await builder.addControllers([UsersController]);Response Helpers (HttpResult)
Steampunk provides a powerful HttpResult class to simplify returning HTTP responses.
// Return 200 OK with an object (auto-serialized to JSON)
return HttpResult.ok({ id: 1, name: "John" });
// Return 201 Created with a Location header
return HttpResult.created(newUser, `/users/${newUser.id}`);
// Return 400 Bad Request with a message
return HttpResult.badRequest("Invalid input");
// Return 404 Not Found
return HttpResult.notFound("User not found");All methods support flexible payloads: string, object, or array.
Utilities
Steampunk includes powerful utility classes inspired by .NET for better collection and date handling.
LINQ
The Linq class provides a fluent API for collection manipulation, mirroring .NET LINQ.
import { Linq } from "@danielgl/steampunk";
const users = [
{ id: 1, name: "Alice", age: 25 },
{ id: 2, name: "Bob", age: 30 },
{ id: 3, name: "Charlie", age: 25 }
];
const names = Linq.from(users)
.where((u) => u.age === 25)
.select((u) => u.name)
.toList(); // ["Alice", "Charlie"]
const hasAdults = Linq.from(users).any((u) => u.age >= 18);DateTime
A robust DateTime class inspired by .NET for consistent date manipulation.
import { DateTime } from "@danielgl/steampunk";
const now = DateTime.Now;
const tomorrow = now.AddDays(1);
const nextMonth = now.AddMonths(1).Date; // Reset to 00:00:00
if (DateTime.IsLeapYear(2024)) {
console.log("Leap year!");
}
const formatted = now.ToString("yyyy-MM-dd HH:mm:ss");Authentication & Authorization
Protect your routes easily using the @Authorize() decorator.
@Authorize() // Multi-level: Class or Method
@ApiController("admin")
export class AdminController extends Controller {
@Get("secret")
public getSecret() {
return HttpResult.ok("shhh!");
}
}Development
Available scripts for contributing or local development:
bun run lint: Run ESLint checks.bun run format: Format code with Prettier.bun run test: Run unit tests with Bun.
Pre-commit hooks are enforced via Husky to ensure code quality!
Made with ❤️ focusing on Developer Experience.
