Project Structure
A Holo-JS project is a normal Next.js, Nuxt, or SvelteKit app with a few extra folders: config/ for settings, server/ for backend code, and storage/ for local files. Holo finds your models, jobs, and other backend files by folder, so you don't register them by hand.
Directory tree
This is a project with every optional package installed. Folders for packages you didn't install are not created.
my-app/
├── config/ # Settings for Holo and each package
│ ├── app.ts
│ ├── database.ts
│ ├── redis.ts
│ └── ... # auth.ts, queue.ts, mail.ts, storage.ts, ...
├── server/
│ ├── models/ # ORM models
│ ├── db/
│ │ ├── migrations/ # Table changes, run with `holo migrate`
│ │ ├── seeders/ # Sample and baseline data
│ │ ├── factories/ # Fake records for tests and seeding
│ │ └── observers/ # Model lifecycle hooks
│ ├── commands/ # Your own `holo` CLI commands
│ ├── jobs/ # Queue jobs
│ ├── events/ # Event definitions
│ ├── listeners/ # Event listeners
│ ├── policies/ # Authorization policies
│ ├── abilities/ # Authorization abilities
│ ├── mail/ # Mail definitions
│ ├── notifications/ # Notifications
│ ├── broadcast/ # Broadcast events
│ ├── channels/ # Broadcast channel authorization
│ └── realtime/ # Realtime query definitions
├── storage/ # SQLite database, uploaded files, sessions
├── .holo-js/ # Generated by Holo. Don't edit.
├── .env # Your local settings and secrets
├── .env.example # The same keys, without secret values
├── package.json
├── tsconfig.json
├── eslint.config.mjs
└── ... # Your framework's files (see below)What you edit and what Holo generates
You work in these places:
config/*.ts.envand the other.env.*files- everything under
server/ - your framework's own files: pages, routes, layouts, components
Holo writes these, and rewrites them whenever your code changes:
.holo-js/generated/: registries of your models, jobs, events, and commands, the database types generated from your migrations, and config types..holo-js/framework/: scripts Holo uses to start your framework..holo-js/runtime/: scratch space for the CLI.
WARNING
Don't edit anything inside .holo-js/. Holo overwrites it on the next holo dev, holo build, or holo prepare. The whole folder is in .gitignore.
The config folder
Each file configures one feature. app.ts, database.ts, and redis.ts are always present. Installing a package adds its file, such as queue.ts or auth.ts. You can also add your own files. See Configuration.
The server folder
All of your backend code lives here.
| Folder | What goes in it | Learn more |
|---|---|---|
server/models | defineModel() definitions, one per file | ORM |
server/db/migrations | defineMigration() files named with a timestamp prefix | Migrations |
server/db/seeders | defineSeeder() files | Seeding |
server/db/factories | Factories for generating fake records | Factories |
server/db/observers | Observers that react to model events | Observers |
server/commands | Custom CLI commands | Custom Commands |
server/jobs | Queue jobs | Writing Jobs |
server/events | Events | Defining Events |
server/listeners | Listeners for those events | Listeners |
server/policies | Per-model authorization policies | Policies |
server/abilities | Standalone authorization checks | Abilities |
server/mail | Mail definitions | Writing Mail |
server/notifications | Notifications | Writing Notifications |
server/broadcast | Events pushed to the browser | Events and Channels |
server/channels | Who may listen on each channel | Events and Channels |
server/realtime | Realtime queries and mutations | Realtime Queries |
You can group files in subfolders, for example server/jobs/reports/send-digest.ts.
You can also add folders of your own, such as server/lib, for shared helpers. Holo ignores folders it doesn't know about.
The storage folder
storage/ holds runtime data:
storage/database.sqlitewhen you use SQLitestorage/app/andstorage/app/public/for files on thelocalandpublicdisksstorage/framework/for file-based sessions and rate-limit counters
storage/ is in .gitignore. Don't commit it.
Framework files
Holo adds its folders next to your framework's normal layout.
app/
├── layout.tsx
├── page.tsx
└── api/ # Route handlers
next.config.ts # Wrapped with withHolo()
proxy.ts # CSRF and route protection (with auth or security)app/
└── app.vue # Pages, layouts, middleware go under app/
server/
├── api/ # Nitro event handlers
└── middleware/ # Server middleware, such as CSRF
shared/
nuxt.config.ts # Lists the @holo-js/adapter-nuxt modulesrc/
├── app.html
├── hooks.ts
├── hooks.server.ts # Your server hooks
└── routes/ # Pages, +server.ts endpoints, load functions
vite.config.ts # sveltekit(withHoloSvelteKit(...))In Nuxt, server/ is shared: Nitro's server/api and server/middleware sit next to Holo's server/models, server/jobs, and so on. Each framework guide explains the details: Next.js, Nuxt, SvelteKit.
Auto-discovery
Holo scans these folders and records what it finds in .holo-js/generated/:
- models, migrations, and seeders
- commands, jobs, events, and listeners
- broadcast events and channels
- policies and abilities
This is how npx holo migrate knows your migrations, how a queue worker finds a job by name, and how holo list shows your custom commands.
Discovery runs automatically:
holo devscans on start and watches for changes while it runs.holo buildscans before building.npx holo preparescans once without starting anything. The project'spreparescript also runs it when your package manager installs dependencies.
If an editor shows stale types after you add a model or migration, run npx holo prepare.
Changing folder locations
The folder paths are set in config/app.ts under paths. Change a value to move that folder:
import { defineAppConfig, env } from '@holo-js/config'
export default defineAppConfig({
name: env('APP_NAME', 'my-app'),
paths: {
models: 'server/models',
migrations: 'server/db/migrations',
seeders: 'server/db/seeders',
commands: 'server/commands',
jobs: 'server/jobs',
events: 'server/events',
listeners: 'server/listeners',
},
})The keys you can set are models, migrations, seeders, factories, observers, commands, jobs, events, listeners, authorizationPolicies, authorizationAbilities, and generatedSchema. Any key you leave out uses the default shown in the tree above.