inviton-backduck
v1.0.9
Published
Runtime-agnostic utilities for building web servers with Express and Bun support
Maintainers
Readme
inviton-backduck
Runtime-agnostic utilities for building web servers with Express and Bun support.
Write your server code once, and it will automatically adapt to the runtime environment (Node.js with Express or Bun native).
Features
- Universal Router: Define routes once, run anywhere
- Controller-based routing with automatic REST mapping
- Runtime detection and automatic adaptation
- Type-safe request/response handling
- Middleware support with chaining
- Static file serving with compression
- API Documentation Generation: Automatic OpenAPI/Swagger and HTML docs from TypeScript controllers
Installation
npm install @inviton/backduckPeer Dependencies
Depending on your runtime, install the appropriate peer dependencies:
For Node.js/Express:
npm install expressFor Bun:
# Bun automatically provides bun-typesQuick Start
1. Define a Controller
import { Route, ControllerBase } from "@inviton/backduck";
interface CreateProductArgs {
name: string;
price: number;
}
@Route("/products")
export class ProductController extends ControllerBase {
// GET /products
async getAll() {
return { products: [] };
}
// GET /products/:id
async get(args: { id: number }) {
return { id: args.id, name: "Product" };
}
// POST /products
async post(args: CreateProductArgs) {
return { id: 1, ...args };
}
// PUT /products/:id
async put(args: { id: number } & CreateProductArgs) {
return { id: args.id, name: args.name, price: args.price };
}
// DELETE /products/:id
async delete(args: { id: number }) {
return { success: true };
}
}2. Create Router and Register Controllers
import { createRouter } from "@inviton/backduck";
import { ProductController } from "./controllers/ProductController";
const router = createRouter();
router.registerController(ProductController);3. Create and Start Server
import { createServer } from "@inviton/backduck";
const server = createServer({
port: 3000,
apiRouters: {
"/api": router,
},
});
await server.start();That's it! Your server will automatically use Bun's native server if running under Bun, or Express if running under Node.js.
API Documentation Generation
Generate OpenAPI/Swagger and HTML documentation from your TypeScript controllers:
1. Create Configuration
Use createApiDocConfig() to build a configurable config (merge with defaults) or implement the ApiDocConfig interface:
// apidoc-config.ts
import { createApiDocConfig } from "@inviton/backduck";
// Option A: Override only what you need (recommended)
export const ApiDocConfig = createApiDocConfig({
title: "My API",
version: "1.0.0",
description: "API documentation for my project",
contact: { name: "My Team", email: "[email protected]" },
servers: {
development: { url: "http://localhost:3000", description: "Local dev" },
production: { url: "https://api.example.com", description: "Production" },
},
apiInterfaces: {
shopApi: { sourceDir: "src/api/shop", basePath: "/api/shop" },
adminApi: { sourceDir: "src/api/admin", basePath: "/api/admin" },
},
output: { specFilename: "openapi.json" },
});2. Generate Documentation
import { ApiDocGenerator } from "@inviton/backduck";
import { ApiDocConfig } from "./apidoc-config";
const result = await ApiDocGenerator.generate({
config: ApiDocConfig,
serverDir: __dirname,
apiType: "shopApi", // optional - defaults to first in apiInterfaces
verbose: true,
});
console.log(
`Generated ${result.endpointsCount} endpoints from ${result.controllersCount} controllers`,
);When using generate(), config is set via ApiDocConfig.configure() for the duration. You can also call ApiDocConfig.configure(config) upfront when using ControllerParser, TypeResolver, etc. directly.
Decorators
@Route(path: string)
Define the base route path for a controller:
@Route("/users")
class UserController extends ControllerBase {
// Routes will be /users, /users/:id, etc.
}@Middleware(middleware)
Add middleware to a controller or method:
@Route("/admin")
@Middleware(authMiddleware)
class AdminController extends ControllerBase {
// All methods require authentication
@Middleware(adminOnlyMiddleware)
async delete(args: { id: number }) {
// This method requires both auth and admin
}
}@ParseRequestArgs(parser)
Custom request argument parsing:
@Route("/search")
class SearchController extends ControllerBase {
@ParseRequestArgs((req) => ({
query: req.query.q as string,
page: parseInt(req.query.page as string) || 1,
}))
async get(args: { query: string; page: number }) {
return { results: [], page: args.page };
}
}@RequestSize(maxSizeInMB)
Set maximum request body size:
@Route("/upload")
@RequestSize(50) // Allow up to 50MB
class UploadController extends ControllerBase {
async post(args: { file: Buffer }) {
// Handle large file upload
}
}Runtime Utilities
The RuntimeUtils class provides helper methods for request parsing:
import { RuntimeUtils, type WebRequest } from "@inviton/backduck";
// Parse query parameters
const id = RuntimeUtils.parseQueryNumber(req, "id");
const tags = RuntimeUtils.parseQueryStringArray(req, "tags");
const active = RuntimeUtils.parseQueryBoolean(req, "active");
const filters = RuntimeUtils.parseQueryJSON(req, "filters");
// Get client IP (handles proxies)
const ip = RuntimeUtils.getIpAddress(req);HTTP Method Mapping
Controller methods are automatically mapped to HTTP methods:
| Method Name | HTTP Method | Route Pattern | Example |
| -------------- | ----------- | ------------- | ---------------------- |
| getAll() | GET | / | GET /products |
| get(args) | GET | /:id | GET /products/123 |
| post(args) | POST | / | POST /products |
| put(args) | PUT | /:id | PUT /products/123 |
| patch(args) | PATCH | /:id | PATCH /products/123 |
| delete(args) | DELETE | /:id | DELETE /products/123 |
Custom method names can be configured via the ApiDocConfig.getHttpMethod() function.
Static File Serving
Serve static files with automatic compression:
import { createServer } from "@inviton/backduck";
const server = createServer({
port: 3000,
apiRouters: { "/api": router },
staticFiles: [
{
urlPath: "/assets",
fsPath: "./public/assets",
cacheControl: "public, max-age=31536000", // 1 year
},
],
});Runtime Detection
import { RUNTIME, IS_BUN } from "@inviton/backduck";
if (IS_BUN) {
console.log("Running on Bun!");
} else {
console.log("Running on Node.js with Express");
}License
MIT
Author
Inviton
