Skip to content

Directory Structure ​

hazejs follows a ThinkPHP / Laravel-style layout: each kind of code has a home, and most of those homes are discovered automatically at build time. You rarely register anything by hand.

Backend-only (api template) ​

The backend source directory is the project root:

my-app/
├─ app/
│  ├─ channel/          Broadcast channel authorization (auto-discovered)
│  ├─ command/          Console commands (auto-discovered)
│  ├─ controller/       HTTP controllers
│  ├─ event/            Events (auto-discovered)
│  ├─ exception/
│  │  └─ Handler.ts     Global exception handler (auto-discovered)
│  ├─ job/              Queue jobs (auto-discovered)
│  ├─ listener/         Event listeners (auto-discovered)
│  ├─ mail/             Mail definitions
│  ├─ message/          Microservice message handlers (auto-discovered; optional)
│  ├─ model/            ORM models
│  ├─ notification/     Notifications (auto-discovered)
│  ├─ policy/           Authorization policies (auto-discovered)
│  ├─ provider/         Service providers (auto-discovered)
│  ├─ resource/         API resources (response transformers)
│  ├─ response/
│  │  └─ Formatter.ts   Response envelope (auto-discovered)
│  ├─ service/          Business services
│  ├─ validate/         Request validation schemas
│  └─ view/             TSX views (Welcome.tsx, Layout.tsx)
├─ config/              Configuration, one file per key (auto-discovered)
├─ database/
│  ├─ factories/        Model factories for tests and seeders (UserFactory.ts)
│  ├─ migrations/       SQL migrations generated by drizzle-kit
│  ├─ schema/           drizzle table definitions (users.ts, auth.ts)
│  └─ seeders/          Seed scripts for haze db:seed
├─ lang/
│  ├─ en/               Translations, one folder per locale (auto-discovered)
│  └─ zh-CN/
├─ modules/             Local modules, same layout as the app (auto-discovered; optional)
├─ route/               Route files (auto-discovered)
├─ schedule/            Scheduled tasks (auto-discovered)
├─ tests/               Vitest tests
├─ .env                 Environment variables (APP_KEY, DATABASE_URL, …)
├─ .env.test            Overrides for tests
├─ haze.config.ts       Toolchain config (srcDir, port, preset, …)
├─ package.json
├─ tsconfig.json
└─ vitest.config.ts

Folders marked optional are not created by the template; add them when you need them. The same goes for app/middleware, app/interceptor and app/repository, described below.

Full-stack (vue / react templates) ​

In a full-stack project the backend moves into server/, and the root is a regular Vite project:

my-app/
├─ server/              Backend source directory (srcDir)
│  ├─ app/              Same folders as above, plus socket/
│  ├─ config/
│  ├─ database/
│  ├─ lang/
│  ├─ route/
│  └─ schedule/
├─ src/                 Frontend (Vue or React)
│  ├─ api.ts            fetch wrapper with the CSRF flow
│  ├─ socket.ts         Socket.IO client
│  ├─ AiChat.vue        Streaming AI chat (AiChat.tsx in React)
│  └─ main.ts / App.vue
├─ tests/               Backend tests
├─ index.html
├─ haze.config.ts       srcDir: 'server'
├─ vite.config.ts       Includes the haze() plugin
├─ tsconfig.json        References tsconfig.app.json and tsconfig.server.json
└─ vitest.config.ts

The location is set by srcDir in haze.config.ts:

ts
// haze.config.ts
import { defineHazeConfig } from '@hazejs/vite'

export default defineHazeConfig({
  srcDir: 'server',
})

Everything described below as app/…, config/… and so on is relative to srcDir. A few things always stay at the project root: .env* files, tests/, stubs/, .haze/, dist/, and the default data/ and storage/ folders.

The ~ alias

~/ always points to srcDir, so import { User } from '~/app/model/User' works the same in both layouts. Configure it with the alias option of the haze() plugin.

What is auto-discovered ​

The Vite plugin builds a manifest of these files with import.meta.glob. Patterns with * scan only the first level of their directory — files in subdirectories are not loaded. The two ** patterns, app/command/** and lang/**, are recursive: the subdirectory becomes part of the command name or translation key.

Path (relative to srcDir)Contents
config/*.tsConfig files. The file name is the config key: config/app.ts → config('app.name')
route/*.tsRoute files exporting defineRoutes(...)
app/provider/*.tsService providers
app/exception/Handler.tsThe global exception handler
app/response/Formatter.tsThe response formatter
app/interceptor/*.tsOutbound interceptors, applied in file-name order
app/policy/*.tsPolicies and abilities
app/job/*.tsQueue jobs. The file name is the job name
app/command/**/*.tsConsole commands. The path is the command name (db/Cleanup.ts → db:cleanup); loaded only by haze and worker.mjs command
app/event/*.tsEvents. The file name is the event name
app/listener/*.tsListeners, run in file-name order
schedule/*.tsScheduled tasks
app/socket/*.tsSocket.IO namespaces, read by @hazejs/socket.io
app/notification/*.tsNotifications. The file name is the notification type
app/channel/*.tsBroadcast channel authorization (defineChannels())
app/message/*.tsMicroservice message handlers, read by @hazejs/microservices
app/view/**/*.tsxTSX views, loaded on demand for ctx.view('Name') and haze down --render; the path is the view name (admin/Dashboard.tsx → admin/Dashboard)
lang/**/*.{ts,json}Translations: lang/<locale>/<file>.ts; the file path is the first part of the key (lang/en/admin/users.ts → admin.users.*)
app/<name>/{route,config,provider,exception,response}/*.tsPer-app modules for multiple apps
modules/<name>/{module.ts,route/*,config/*,schedule/*,app/<kind>/*}Local modules: the same kinds as the app (provider, interceptor, policy, job, event, listener, notification, channel, socket, message, and command/**)

Adding or deleting one of these files in dev regenerates the manifest — no restart needed.

Directory reference ​

app/controller ​

Controllers receive the request, call services and return data. They are not discovered: routes reference them directly, as in r.get('/hello', [GreetingController, 'index']). Subdirectories are fine (app/controller/admin/UserController.ts). See Controllers.

app/service ​

Business logic that does not know about HTTP. Services are classes resolved by the container with inject(GreetingService). Bind them explicitly in a provider when you need a specific scope. See Service Container.

app/repository ​

Optional data-access layer, generated with haze make:repository. Not part of the templates.

app/model ​

ORM models built on the drizzle tables in database/schema, for example class User extends Model.define(users). See Models.

app/validate ​

Standard Schema validators (valibot by default; zod, arktype and others also work) used by .validate({ body, query, params }) and validate(). See Requests & Validation.

app/middleware ​

Class or function middleware. Middleware is not discovered: register an alias in config/middleware.ts and use it by name with .middleware('name'). See Middleware.

app/interceptor ​

Outbound interceptors receive the final Response and can change headers or replace it. Discovered, applied in file-name order.

app/policy ​

Authorization policies (definePolicy(Model, { … })) and model-free abilities (defineAbilities({ … })). Discovered and registered with the gate; ability names are included in the generated types. See Authorization.

app/job ​

Queue jobs defined with defineJob(). The file name becomes the job name. See Queues.

app/command ​

Console commands defined with defineCommand(), run with haze <name> or node dist/worker.mjs command <name>. Subdirectories are allowed and become part of the name. The templates ship Inspire.ts, run as haze inspire. See Console Commands.

app/event and app/listener ​

Events (defineEvent()) and listeners (defineListener(Event, handler)). Both are discovered; listeners can run synchronously or on the queue. See Events.

app/mail ​

Mail definitions (defineMail()), imported where you send them: Mail.to(user.email).send(WelcomeMail, payload). Not discovered. See Mail.

app/view ​

TSX views: components rendered to HTML on the server with ctx.view(<Home />). Import them where you render them, or render by name with ctx.view('admin/Dashboard', props). Layouts are components too. haze make:view Home creates app/view/Home.tsx.

app/notification ​

Notifications defined with defineNotification(), sent with notify(user, InvoicePaid(payload)) over mail, database, broadcast, Slack, webhooks and chat robots. They are discovered only to name them: the file name becomes the type stored in the database and pushed to the browser. See Notifications.

app/channel ​

Authorization for private and presence broadcast channels (defineChannels((c) => { c.private('orders.{id}', …) })). Every file is loaded; when at least one exists, the /broadcasting endpoints are enabled. See Broadcasting.

app/resource ​

API resources (defineResource((user, ctx) => ({ … }))) that decide which fields a model exposes: UserResource.make(user) for one item, UserResource.collection(await User.paginate(page, 20)) for lists and pages. Not discovered — import them in controllers. The templates ship UserResource.ts. See API Resources.

app/message ​

Microservice message handlers (defineMessageHandler('orders.find', handler)), registered by microservicesProvider() from @hazejs/microservices. Each file default-exports a handler or an array of handlers. They only receive messages while a listener runs (haze microservice:listen, or autoListen). See Microservices.

app/socket ​

Socket.IO namespaces (defineNamespace()), loaded by @hazejs/socket.io when its provider is registered. The file name is the namespace (chat.ts → /chat, index.ts → /). See Socket.IO.

app/provider ​

Service providers with register() (bind services) and boot() (startup logic). Every file is loaded — for example DatabaseProvider.ts (export default databaseProvider()) registers the database.

app/exception ​

Handler.ts extends ExceptionHandler to customize how errors are reported (report()) and rendered (render()). See Errors & Logging.

app/response ​

Formatter.ts extends ResponseFormatter to change the success and error envelope ({ code, message, data } by default). See Responses.

config/ ​

One file per config key, usually with a defineXxxConfig() helper. See Configuration.

lang/ ​

Translation files, one folder per locale: lang/en/messages.ts default-exports a nested object read with t('messages.welcome'). Subdirectories and JSON files work too. With config/i18n.ts present, validation errors and framework messages are translated as well. The templates ship en and zh-CN. See Localization.

route/ ​

Centralized route definitions. Split them however you like (api.ts, ws.ts, …) — every file is loaded. See Routing.

database/ ​

  • schema/ — drizzle table definitions. drizzle-kit reads these files directly, so they should only import from drizzle-orm (do not use the ~ alias here).
  • migrations/ — SQL files and meta/_journal.json written by haze db:generate. Non-default connections use migrations/<connection>/. Migrations are bundled into the production build.
  • seeders/ — files with a default-exported function, run in file-name order by haze db:seed.
  • factories/ — model factories (defineFactory()), imported by tests and seeders; nothing loads them automatically.

See Database.

schedule/ ​

Task schedules defined with defineSchedule((s) => { … }). See Task Scheduling.

modules/ ​

Local modules: each modules/<name>/ folder uses the same layout as the app (route/, config/, app/…, schedule/, database/) plus an optional module.ts. They are enabled by default; config/modules.ts disables or configures them and enables module packages. Create one with haze make:module <name>.

tests/ ​

Vitest tests using @hazejs/testing. Always at the project root, even in full-stack projects. See Testing.

Generated and runtime folders ​

PathPurposeIn git?
.haze/Generated files: types for config keys, routes and abilities (types/haze.d.ts), translation keys (types/i18n.d.ts), the route list for the typed client (routes.ts); plus the last build record (build.json), local preview data and the REPL historyNo
dist/Build output (outDir in haze.config.ts)No
data/The template's SQLite database (DATABASE_URL=file:./data/app.db)No
storage/Local file storage disks (storage/app, storage/app/public), and the maintenance state file storage/framework/downNo
stubs/Your customized generator templates, created by haze stub:publishYes

Include .haze/types/**/*.d.ts in your tsconfig.json (the templates already do) so typed config keys, route names, abilities and translation keys are picked up by the editor. haze dev regenerates these files as you edit; run haze typegen in CI.

Released under the MIT License.