honovajs
v0.0.1
Published
Nest-like framework for Cloudflare Workers powered by Hono with modules, decorators, DI, and typed database middleware.
Maintainers
Readme
Honova
Nest-like framework for Cloudflare Workers built on top of Hono.
Honova provides a module system, decorators, dependency injection, route metadata, request-aware lifecycle, and typed database middleware so you can build structured APIs on the edge without bringing a full Node-centric runtime.
Status
- Package version:
0.0.1 - Runtime target: Cloudflare Workers (also works anywhere Hono runs)
- Module format: ESM
Feature Checklist
Core Framework
- [x]
@Module()withcontrollers,providers, andimports - [x]
@Controller()with route prefixing - [x] HTTP method decorators:
@Get,@Post,@Put,@Patch,@Delete,@Options,@Head - [x] Dependency Injection container
- [x]
@Injectable()scopes (singleton,request,transient) - [x]
@Inject()token-based constructor injection - [x] Provider auto-inference by constructor parameter name
- [x] Request-aware provider resolution (
resolveWithContext) - [x]
OnModuleInitlifecycle hook (sync) - [x] Global middleware registration
- [x] Controller-level and route-level middleware via
@UseMiddleware - [x] Built-in
not_foundandinternal_errorJSON responses
Security and Observability
- [x] CORS integration (
hono/cors) - [x] Optional secure headers (
hono/secure-headers) - [x] Request ID propagation (
x-request-idby default) - [x] Access logs with log level control (
debug,info,warn,error) - [x] Header redaction in debug logs
Authentication Middleware
- [x] Bearer auth middleware (
UseBearerAuth,BearerAuth) - [x] API key middleware (
UseApiKey,ApiKeyAuth) - [x] Auth principal injection into context (
c.set("auth", principal)) - [x] Overrideable auth error handling
Database Layer
- [x]
defineDatabase()typed config helper - [x]
DatabaseManagerwith per-request lifecycle - [x] Single and multi-connection support
- [x]
defaultConnectionenforcement for multi-DB setups - [x] URL resolution from static URL, env binding, or async resolver
- [x] Eager connection initialization
- [x] Connection cleanup hooks
- [x] Context bindings:
c.db,c.get("db"),c.get("dbConnections") - [x] Adapter helpers: Drizzle, Prisma, MongoDB (
createMongoDbAdapter) with legacy alias (createMondelAdapter)
Tests
- [x] DI behavior tests
- [x] Provider validation tests (
@Injectablerequired) - [x] Lifecycle behavior tests (
onModuleInitwith request context) - [x] Database manager and middleware tests
Planned / Missing Features
- [x] Request-scoped and transient instance caching semantics validated by tests
- [ ] Async
onModuleInitsupport in runtime - [ ] Validation pipes and DTO validation layer
- [ ] Guard/interceptor abstraction (Nest-style)
- [ ] Exception filter abstraction
- [ ] First-class OpenAPI/Swagger generation
- [ ] CLI scaffolding (
create-honova-app, generators) - [ ] Official Cloudflare starter templates (D1, KV, R2 examples)
- [ ] More auth primitives (JWT helper, role/permission guard, session middleware)
- [ ] E2E test suite with Worker runtime integration
- [ ] Benchmark/performance suite and guidance
- [ ] Complete API docs website
Installation
npm i honovajspnpm add honovajsyarn add honovajsbun add honovajsQuick Start
import { Controller, Get, Injectable, Module, createApp } from "honovajs";
@Injectable()
class HealthService {
getStatus() {
return { ok: true };
}
}
@Controller("/health")
class HealthController {
constructor(private readonly healthService: HealthService) {}
@Get("/")
handle(): Response {
return Response.json(this.healthService.getStatus());
}
}
@Module({
controllers: [HealthController],
providers: [HealthService],
})
class AppModule {}
const app = createApp({ basePath: "/api" });
app.registerModule(AppModule);
export default {
fetch: app.fetch,
};Core Concepts
Modules
@Module() organizes app boundaries:
controllers: classes that expose HTTP handlersproviders: services managed by the DI containerimports: other modules to compose features
All providers listed in providers must have @Injectable().
Controllers and Routes
@Controller("/prefix") defines a route prefix. Route handlers are defined with HTTP decorators.
@Controller("/users")
class UserController {
@Get("/")
list() {
return Response.json([]);
}
}Dependency Injection
Providers are resolved through the container. Constructor dependencies can be resolved by:
- explicit token with
@Inject(token) - inferred class token by constructor parameter name
Strict DI mode is enabled by default and throws on unresolved dependencies.
Provider scopes:
singleton: one instance per app containerrequest: one instance per request context (reused within the same request)transient: new instance on each resolution
Lifecycle: OnModuleInit
A provider can implement onModuleInit(context?).
Current runtime behavior:
- It runs when the provider is resolved for the first time.
- For singleton providers, it runs once per app instance.
- Request context is passed when the provider is resolved during a request.
- It must be synchronous in
0.0.1.
Middleware
Global Middleware
const app = createApp({
globalMiddlewares: [async (c, next) => { await next(); }],
});Decorator Middleware
import { Controller, Get, UseMiddleware } from "honovajs";
const requireHeader = async (c: any, next: any) => {
if (!c.req.header("x-trace")) {
return c.json({ error: "missing x-trace" }, 400);
}
await next();
};
@UseMiddleware(requireHeader)
@Controller("/demo")
class DemoController {
@Get("/")
handle() {
return Response.json({ ok: true });
}
}Security and Observability
Configure at app creation:
const app = createApp({
security: {
cors: { origin: ["https://app.example.com"] },
secureHeaders: true,
},
observability: {
requestIdHeader: "x-request-id",
enableAccessLogs: true,
logLevel: "info",
redactHeaders: ["authorization", "cookie", "x-api-key"],
},
});Authentication
Bearer Token
import { Controller, Get, UseBearerAuth } from "honovajs";
@UseBearerAuth({
verify: async (token) => (token === "valid-token" ? { id: "user_1" } : null),
})
@Controller("/me")
class MeController {
@Get("/")
handle() {
return Response.json({ ok: true });
}
}API Key
import { Controller, Get, UseApiKey } from "honovajs";
@UseApiKey({
header: "x-api-key",
verify: async (key) => (key === "secret" ? { id: "service_1" } : null),
})
@Controller("/internal")
class InternalController {
@Get("/")
handle() {
return Response.json({ ok: true });
}
}Database Integration
Define Connections
import { createDbManagerMiddleware, defineDatabase } from "honovajs";
type Env = { DATABASE_URL: string; ANALYTICS_URL: string };
const database = defineDatabase<Env>()({
lifecycle: { strategy: "request" }, // default
defaultConnection: "main",
connections: [
{
connectionName: "main",
adapter: {
name: "custom",
connect: async ({ url }) => ({ url }),
},
urlFromEnv: "DATABASE_URL",
// default connection is always initialized per request
},
{
connectionName: "analytics",
adapter: {
name: "custom",
connect: async ({ url }) => ({ url }),
},
urlFromEnv: "ANALYTICS_URL",
eager: true, // optional; initialize this non-default connection on every request
},
],
});
const app = createApp<Env>({
database,
});Inside handlers:
c.dbpoints to the default connection and also exposes named connections.c.get("dbConnections")returns all named connections.- Non-default connections are lazy by default (
eager: false). lifecycle.strategydefaults to"request"(cleanup after each request). Use"manual"only when you intentionally manage connection lifecycle yourself.
Cloudflare Workers Deployment
Honova exposes app.fetch, so Worker export is direct:
export default {
fetch: app.fetch,
};Use your standard Worker workflow with Wrangler:
npm run build
wrangler deployAPI Surface
Main exports include:
- App/runtime:
createApp,Application - Decorators:
Module,Controller,Injectable,Inject,UseMiddleware, HTTP method decorators - DI helper:
inject - Database:
defineDatabase,DatabaseManager,createDbManagerMiddleware,createDrizzleAdapter,createPrismaAdapter,createMongoDbAdapter(legacycreateMondelAdapter), helpers fromdatabase/types - Auth middleware:
UseBearerAuth,BearerAuth,UseApiKey,ApiKeyAuth
Development Scripts
npm run typecheck
npm run test
npm run buildPublish Checklist (npm)
- [x] Version set to
0.0.1 - [x] ESM build output in
dist/ - [x] Type declarations emitted
- [x] Package
filesrestricted todist - [x]
prepublishOnlyruns typecheck + tests + build - [ ] npm organization metadata (
repository,bugs,homepage) if needed - [ ]
npm publish --access public
License
MIT
