Honest Skill
Build Honest.js apps with CLI, decorators, modules, and DI. Honest is a TypeScript-first web framework on Hono with a Nest-like architecture.
Setup
Using Honest CLI
bun add -g @honestjs/cli
honestjs new my-project # aliases: honest, hnjs
cd my-project
bun dev
Manual setup
bun add honestjs hono reflect-metadata
Bootstrap (entry file):
import "reflect-metadata";
import { Application } from "honestjs";
import { AppModule } from "./app.module";
const { app, hono } = await Application.create(AppModule, {
routing: { prefix: "api", version: 1 },
});
export default hono;
Always import reflect-metadata once before any Honest decorators. Export the
hono instance for your server (e.g. Cloudflare Workers, Node, Bun).
CLI
- New project:
honestjs new <project-name>- options:-t|--template(name or local path:./path,~/path),-p|--package-manager,--typescript,--eslint,--prettier,--docker,--git,--install,-y|--yes,--offline,--refresh-templates - List templates:
honestjs list--j|--json,-c|--category,-t|--tag,-l|--local <path>(list from local repo or single template) - Info:
honestjs info--l|--local <path>(show templates from local) - Generate:
honestjs generate <schematic> <name>(aliasg) - schematics:controller|c,service|s,module|m,view|v,middleware|c-m,guard|c-g,filter|c-f,pipe|c-p. Options:-p|--path,--flat,--force(overwrite existing files),--skip-import,--export
Prefer honestjs new for new apps and honestjs generate for adding
controllers, services, modules, guards, pipes, or filters.
Local templates: Use a local path for --template to scaffold from a local
templates repo (directory with templates.json) or single template (directory
with template.json + files/). Examples:
honestjs new my-app -t ./templates,
honestjs new my-app -t ./templates/barebone,
honestjs list --local ./templates.
Application and routing
- Create app:
Application.create(RootModule, options)returns{ app, hono }. - Routing:
routing: { prefix?: string, version?: number | VERSION_NEUTRAL | number[] }- e.g.
prefix: 'api',version: 1→/api/v1/....
- e.g.
- Global components:
components: { middleware?, guards?, pipes?, filters? }- applied to all routes. - Plugins:
plugins?: PluginEntry[]- each entry can be a plain plugin or{ plugin, preProcessors?, postProcessors? }. Processors receive(app, hono, ctx)wherectxisapp.getContext()(application context). Order: preProcessors →beforeModulesRegistered;afterModulesRegistered→ postProcessors. - Custom handlers:
onError?,notFound?on options. - Debug/strict:
debug: { routes?, plugins? },strict: { requireRoutes? },deprecations: { printPreV1Warning? }- see Configuration. - Hono access:
app.getApp()for the underlying Hono instance;app.getRoutes()for route info. - Startup validations: duplicate method+path routes fail at registration; strict mode can fail startup when zero routes are registered.
- Application context (registry):
app.getContext()- app-scoped key-value store for the whole app (bootstrap, services, any code withapp). Useget<T>(key),set<T>(key, value),has(key),delete(key),keys(). Namespace keys (e.g.app.config,rpc.artifact,openapi.spec). Use for pipeline/config or shared data that outlives a request. Not Hono request context: that is per-request and injected via@Ctx()(request, response, env, request-scoped variables).
Modules and DI
- Module:
@Module({ controllers?, services?, imports? })- list controller/service classes and imported modules. - Service:
@Service()- marks a class as injectable singleton; inject via constructor in controllers or other services. - Current DI shape: constructor injection with concrete class types.
@Module({
controllers: [UsersController],
services: [UsersService],
imports: [OtherModule],
})
class UsersModule {}
Controllers and routes
- Controller:
@Controller(route?)- base path for all handlers in the class. - HTTP methods:
@Get(path?),@Post(path?),@Put(path?),@Delete(path?),@Patch(path?),@Options(path?),@All(path?)- path is optional (default'').
Parameter decorators: use on handler arguments.
| Decorator | Purpose |
|---|---|
@Body(key?) | Request body (JSON); optional key to get one property |
@Param(name?) | Route param(s) |
@Query(name?) | Query param(s) |
@Header(name?) | Header(s) |
@Req() / @Request() | Hono request |
@Res() / @Response() | Hono response |
@Ctx() / @Context() | Hono context |
@Var(name) / @Variable(name) | Context variable |
Example:
@Controller("users")
class UsersController {
@Get(":id")
getOne(@Param("id") id: string, @Ctx() ctx: Context) {
return ctx.json({ id });
}
@Post()
async create(@Body() body: CreateUserDto) {
return { created: body };
}
}
Notes:
@Body()is safe to use multiple times in the same handler (request JSON is reused per request).- Returning a native
Responsefrom a handler is supported and passed through directly.
Pipeline
Apply at class or method level:
- UseMiddleware(...middleware) - runs before the handler.
- UseGuards(...guards) - determines if the request is allowed.
- UsePipes(...pipes) - transform input before the handler.
- UseFilters(...filters) - handle exceptions for the controller/method.
Same pattern as Nest: class-level applies to all methods; method-level overrides or adds.
Ecosystem
@honestjs/rpc-plugin
bun add @honestjs/rpc-plugin
- Typed RPC client - analyzes HonestJS controllers and generates a type-safe
TypeScript client. Register:
plugins: [RPCPlugin]orplugins: [new RPCPlugin(options)]. - Options:
controllerPattern(glob for controller files),tsConfigPath,outputDir(default./generated/rpc),generateOnInit(defaulttrue),generators(optional array of custom generators),mode(strict/best-effort),logLevel(silent/error/warn/info/debug),customClassMatcher(optional controller discovery override),failOnSchemaError,failOnRouteAnalysisWarning. UsecontrollerPatternif controllers live outside the defaultsrc/modules/*/*.controller.ts. - Generators: when
generatorsis omitted, plugin uses built-inTypeScriptClientGenerator; when defined, only provided generators run. - Generated client:
ApiClientwith controller-namespaced methods; call with{ params?, query?, body?, headers? }. Usenew ApiClient(baseUrl, { fetchFn? })for custom fetch (testing, retries, interceptors).setDefaultHeaders()for auth. Errors viaApiError. - Manual generation:
generateOnInit: falsethenawait rpcPlugin.analyze({ force?: boolean, dryRun?: boolean })when needed.dryRun: trueruns analysis without generating client files. Controllers should use@Body(),@Param(),@Query()with typed DTOs/interfaces for best client inference. - OpenAPI/Swagger: RPC plugin does not generate OpenAPI specs. It publishes
routes/schemas artifact to app context (default key:
rpc.artifact) withartifactVersion: "1". API Docs plugin defaults to that key - usenew ApiDocsPlugin()with RPC, or passartifactfor a custom key or direct{ artifactVersion?, routes, schemas }object. - Diagnostics output: plugin writes
rpc-diagnostics.jsonin output dir (mode, cache status, warnings, counts).
@honestjs/api-docs-plugin
bun add @honestjs/api-docs-plugin
- OpenAPI + Swagger UI - generates OpenAPI spec from an artifact and serves
JSON + Swagger UI. Register:
plugins: [new ApiDocsPlugin()]orplugins: [new ApiDocsPlugin(options)]. With RPC,artifactdefaults to'rpc.artifact'so no options are needed. - Artifact source:
artifactis optional (default: context key'rpc.artifact'). Can pass another context key or a direct object{ routes, schemas }. Put the producer plugin (e.g. RPCPlugin) before ApiDocsPlugin in the plugins array when using a context key. - Options:
title,version,description,servers(OpenAPI metadata);openApiRoute(default/openapi.json),uiRoute(default/docs),uiTitle(default'API Docs'),reloadOnRequest(defaultfalse),onOpenApiRequestandonUiRequest(optional auth hook points). - Programmatic:
fromArtifactSync(artifact, options)andwrite(spec, path)for generating spec files; types:OpenApiArtifactInput,OpenApiDocument,OpenApiGenerationOptions. - Artifact contract: when
artifactVersionexists, supported value is"1"; unsupported versions fail with explicit error.
@honestjs/middleware
bun add @honestjs/middleware
- Application config:
components: { middleware: [new LoggerMiddleware(), new CorsMiddleware({ origin: '...' }), new SecureHeadersMiddleware()] }. - Wrap Hono middleware:
new HonoMiddleware(poweredBy())ornew HonoMiddleware(async (c, next) => { ... }).
@honestjs/pipes
bun add @honestjs/pipes
- PrimitiveValidationPipe - validates and transforms primitive types
(String, Number, Boolean) on route params/query/body. Register globally:
components: { pipes: [new PrimitiveValidationPipe()] }.
@honestjs/class-validator-pipe
bun add @honestjs/class-validator-pipe
- ClassValidatorPipe - validates and transforms DTOs with class-validator
and class-transformer. Define DTOs with decorators (
@IsString(),@IsEmail(),@MinLength(),@IsOptional(), etc.), then register:components: { pipes: [new ClassValidatorPipe()] }. Use@Body()with the DTO type in handlers.
http-essentials
bun add http-essentials
- Status/phrase:
HttpStatus.OK,HttpPhrase.NOT_FOUND,httpPhraseByStatus,httpStatusByPhrase. - Exceptions:
NotFoundException,BadRequestException,UnauthorizedException, etc. - throw in handlers or use in filters; include default messages and custom message arg.
Use in guards and exception filters for consistent HTTP responses.
MVC and views
HonestJS supports server-side rendered views with Hono JSX. Use the mvc
template (honestjs new my-app -t mvc) for full-stack apps. Other templates:
barebone, blank. Use barebone and enable RPC / API docs options for OpenAPI
and type-safe client generation. Views use @View(), @Page(), Layout, and
JsxRendererMiddleware - see
MVC docs.
Guidelines
- Use
honestjs newfor new projects; usehonestjs generatefor controllers, services, modules, guards, pipes, filters. Use--forceto overwrite existing files. - Always
import 'reflect-metadata'once at entry before any Honest code. - Export
honofrom the entry used by your server (Worker, Node, Bun). - Honest is pre-v1: API may change; avoid relying on undocumented behavior.
- For Hono-specific behavior (middleware, adapters), use
app.getApp()and the Hono API.