Modules
Every module lives in src/modules/<module-name> and can contain:
src/modules/<module>/
├── controllers/
├── routes/
├── database/
│ ├── models/
│ └── seeders/
├── jobs/
└── console/Creating a Module
npm run maker module:make blogpnpm maker module:make blogyarn maker module:make blogbun maker module:make blogAdding Components
npm run maker module:make-controller blog post
npm run maker module:make-route blog post
npm run maker module:make-model blog post
npm run maker module:make-seeder blog post
npm run maker module:make-job blog process-comment
npm run maker module:make-console blog cleanuppnpm maker module:make-controller blog post
pnpm maker module:make-route blog post
pnpm maker module:make-model blog post
pnpm maker module:make-seeder blog post
pnpm maker module:make-job blog process-comment
pnpm maker module:make-console blog cleanupyarn maker module:make-controller blog post
yarn maker module:make-route blog post
yarn maker module:make-model blog post
yarn maker module:make-seeder blog post
yarn maker module:make-job blog process-comment
yarn maker module:make-console blog cleanupbun maker module:make-controller blog post
bun maker module:make-route blog post
bun maker module:make-model blog post
bun maker module:make-seeder blog post
bun maker module:make-job blog process-comment
bun maker module:make-console blog cleanupOpenAPI Mode
The generated controller, route, and schema files adapt to your OPEN_API environment variable. The CLI reads process.env.OPEN_API at scaffold time (env-db.mjs:openApiEnabled()) and selects the appropriate stub templates.
| Aspect | OPEN_API=true | OPEN_API=false (default) |
|---|---|---|
| Controller validation | c.req.valid("param") / c.req.valid("json") — Hono's built-in validation driven by route schema | await validate(Schema, data) — manual validation call via @/framework/facade.js |
| Schema file | Full set: ItemSchema, CreateSchema, UpdateSchema, IdParamsSchema, response schemas (ListResponse, Response, Message) | Minimal: only CreateSchema, UpdateSchema, IdParamsSchema (input-only) |
| Route file | Uses createRoute() with .api() — each route has metadata (path, method, tags, request params/body, response codes) | Uses direct verb methods .get("/:id", handler) — no metadata, no response schemas |
| Controller imports | No validation import needed | import { validate } from "@/framework/facade.js" |
| Generated API docs | Routes appear in Scalar UI at /api-docs with full request/response schemas | No auto-generated documentation |
OPEN_API=true — OpenAPI Stubs
Controller (controller/openapi.ts.stub):
import type { Handler } from "hono";
export const show: Handler = async (c: any) => {
const params = c.req.valid("param");
return c.json({
message: "Post fetched successfully",
data: { id: params.id, name: "" },
});
};Validation comes from the route definition — the controller simply accesses validated data via c.req.valid().
Route (route/api.ts.stub):
import {
createRoute,
group,
HttpStatusCodes,
jsonContent,
} from "@/framework/facade.js";
const showRoute = createRoute({
path: "/{id}",
method: "get",
tags: ["Blog"],
request: { params: PostIdParamsSchema },
responses: {
[HttpStatusCodes.OK]: jsonContent(PostResponseSchema, "post details"),
},
});
export default group().api(showRoute, show);Schema (controller/schema.ts.stub):
export const PostItemSchema = z.object({ id: z.number(), name: z.string() });
export const PostResponseSchema = z.object({
message: z.string(),
data: PostItemSchema,
});
// + CreateSchema, UpdateSchema, IdParamsSchema, ListResponseSchema, MessageSchemaOPEN_API=false — Plain Stubs
Controller (controller/plain.ts.stub):
import type { Handler } from "hono";
import { validate } from "@/framework/facade.js";
import {
CreatePostSchema,
UpdatePostSchema,
PostIdParamsSchema,
} from "./post.schema.js";
export const show: Handler = async (c: any) => {
const params = await validate(PostIdParamsSchema, c.req.param());
return c.json({
message: "Post fetched successfully",
data: { id: params.id, name: "" },
});
};Validation is explicit — the controller calls validate() directly on the raw input.
Route (route/plain.ts.stub):
import { group } from "@/framework/facade.js";
export default group()
.get("/", index)
.get("/:id", show)
.post("/", store)
.put("/:id", update)
.delete("/:id", destroy);Schema (controller/schema.plain.ts.stub):
export const CreatePostSchema = z.object({ name: z.string().min(1) });
export const UpdatePostSchema = z.object({ name: z.string().min(1) });
export const PostIdParamsSchema = z.object({
id: z.coerce.number().int().positive(),
});
// No response schemas — only input validation schemasRoute Auto-Linking
When you add a new route with module:make-route, the CLI automatically links it to the most recently modified controller in the module:
npm run maker module:make-route blogpnpm maker module:make-route blogyarn maker module:make-route blogbun maker module:make-route blogThe linker (resolveRouteControllerName in core.mjs) works like this:
- If you specify a controller name (e.g.
module:make-route blog post), it checks forcontrollers/post.controller.tsandcontrollers/post.schema.ts - If those exist, it uses them. If not, it scans the module's
controllers/directory and picks the most recently modified.controller.tsfile - Falls back to the module name
The generated route file imports the controller's handlers and schemas:
import {
index,
show,
store,
update,
destroy,
} from "@/modules/blog/controllers/post.controller.js";
// In OPEN_API mode, also imports schemas
import {
PostIdParamsSchema,
PostListResponseSchema,
} from "@/modules/blog/controllers/post.schema.js";The route uses the OPEN_API setting active at scaffold time to decide the route style:
OPEN_API=true | OPEN_API=false | |
|---|---|---|
| Route registration | .api(createRoute({...}), handler) — each route carries full request/response schema metadata | .get("/", handler) — plain verb method, no metadata |
| Schema import | Full set: params, body, response schemas | None — controller handles validation |
| Controller handlers wired | All 5 CRUD: index, show, store, update, destroy | All 5 CRUD: index, show, store, update, destroy |
Important: The route file is a wrapper — it does not contain business logic. It defines the HTTP metadata (path, method, params, response codes) and delegates execution to the controller. This keeps your controllers framework-agnostic and your route definitions declarative.
Switching Modes
To scaffold in plain mode, set OPEN_API=false before running the generator:
OPEN_API=false npm run maker module:make-controller blog post
OPEN_API=false npm run maker module:make-route blog postOPEN_API=false pnpm maker module:make-controller blog post
OPEN_API=false pnpm maker module:make-route blog postOPEN_API=false yarn maker module:make-controller blog post
OPEN_API=false yarn maker module:make-route blog postOPEN_API=false bun maker module:make-controller blog post
OPEN_API=false bun maker module:make-route blog postTo create a route that links to a specific controller:
# Links to controllers/post.controller.ts + controllers/post.schema.ts
npm run maker module:make-route blog post
# Saves as routes/post.ts (instead of api.ts)
npm run maker module:make-route blog --force
# Overwrites routes/api.ts if it already exists# Links to controllers/post.controller.ts + controllers/post.schema.ts
pnpm maker module:make-route blog post
# Saves as routes/post.ts (instead of api.ts)
pnpm maker module:make-route blog --force
# Overwrites routes/api.ts if it already exists# Links to controllers/post.controller.ts + controllers/post.schema.ts
yarn maker module:make-route blog post
# Saves as routes/post.ts (instead of api.ts)
yarn maker module:make-route blog --force
# Overwrites routes/api.ts if it already exists# Links to controllers/post.controller.ts + controllers/post.schema.ts
bun maker module:make-route blog post
# Saves as routes/post.ts (instead of api.ts)
bun maker module:make-route blog --force
# Overwrites routes/api.ts if it already existsOr set it in your .env to persist the choice. The CLI will print which mode it used at scaffold time.
Example and notification stubs also respect the
OPEN_APIflag — they generate OpenAPI or plain routes/schemas just like regular module stubs.
Auto-Discovery
nexgen automatically discovers and registers:
- Routes from
*/routes/*.ts - Jobs from
*/jobs/*.tswithshouldQueue - Schedules from
*/schedules/*.tsor*/console/*.tswithdefineSchedule - Models from
*/database/models/*.ts - Seeders from
*/database/seeders/*.ts
