Project Structure
File convention layout, discovery rules, and generated output.
QUESTPIE uses a file convention — your project structure determines what gets discovered and wired into the runtime. No manual registration required.
Standard Layout
my-app/
├── questpie.config.ts # CLI config (re-exports server config)
├── src/
│ ├── questpie/
│ │ ├── server/
│ │ │ ├── questpie.config.ts # Runtime config (DB, plugins, adapters)
│ │ │ ├── modules.ts # Module dependencies
│ │ │ ├── auth.ts # Authentication config
│ │ │ ├── locale.ts # Content locale config
│ │ │ │
│ │ │ ├── collections/ # One file per collection
│ │ │ │ ├── posts.ts
│ │ │ │ ├── categories.ts
│ │ │ │ └── comments.ts
│ │ │ │
│ │ │ ├── globals/ # One file per global
│ │ │ │ └── site-settings.ts
│ │ │ │
│ │ │ ├── routes/ # App routes (JSON or raw)
│ │ │ │ ├── create-booking.ts
│ │ │ │ └── get-stats.ts
│ │ │ │
│ │ │ ├── jobs/ # Background tasks
│ │ │ │ └── send-email.ts
│ │ │ │
│ │ │ ├── services/ # Singleton services
│ │ │ │ └── blog.ts
│ │ │ │
│ │ │ ├── blocks/ # Content blocks (admin plugin)
│ │ │ │ ├── hero.ts
│ │ │ │ └── gallery.ts
│ │ │ │
│ │ │ ├── emails/ # Email templates
│ │ │ │ └── appointment-confirmation.ts
│ │ │ │
│ │ │ ├── config/ # Typed configuration files
│ │ │ │ ├── auth.ts # Auth config (Better Auth)
│ │ │ │ ├── app.ts # App config (locale, access, hooks)
│ │ │ │ ├── admin.ts # Admin config (sidebar, dashboard, branding)
│ │ │ │ └── openapi.ts # OpenAPI config
│ │ │ │
│ │ │ └── .generated/ # Codegen output (never edit)
│ │ │ ├── index.ts # App instance + types
│ │ │ └── module.ts # Merged module
│ │ │
│ │ └── admin/ # Admin client customizations
│ │ ├── admin.ts # Re-exports generated admin config
│ │ ├── hooks.ts # Typed React hooks
│ │ ├── .generated/ # Codegen output (never edit)
│ │ │ └── client.ts # Admin client config
│ │ └── blocks/ # Block renderers (React)
│ │ ├── hero.tsx
│ │ └── gallery.tsx
│ │
│ ├── lib/
│ │ └── client.ts # Client SDK setup
│ │
│ └── routes/ # App routes (framework-specific)
│ └── api/
│ └── $.ts # API catch-all handlerDiscovery Rules
Codegen discovers files based on directory name and export pattern:
| Directory | Export | Key derivation | Example |
|---|---|---|---|
collections/ | Default or named | Filename → camelCase | blog-posts.ts → blogPosts |
globals/ | Default or named | Filename → camelCase | site-settings.ts → siteSettings |
routes/ | Default | Filename → camelCase/slash path | create-booking.ts → createBooking |
jobs/ | Default | Filename → camelCase | send-email.ts → sendEmail |
routes/ (raw) | Default | Filename → slash path | webhook.ts → webhook |
services/ | Default | Filename → camelCase | blog.ts → blog |
blocks/ | Named exports | Export name | export const hero → hero |
emails/ | Default | Filename → camelCase | appointment-confirmation.ts → appointmentConfirmation |
Single-file conventions
Some configs are single files instead of directories:
| File | Purpose |
|---|---|
questpie.config.ts | Runtime config (DB, plugins, adapters) |
modules.ts | Module dependencies array |
config/auth.ts | Auth config via authConfig() (Better Auth options) |
config/app.ts | App config via appConfig() (locale, access, hooks) |
config/admin.ts | Admin config via adminConfig() (sidebar, dashboard, branding) |
config/openapi.ts | OpenAPI config via openApiConfig() (spec info, Scalar) |
Nested routes
Routes support nested directories for namespacing:
routes/
├── booking/
│ ├── create.ts → client.routes.booking.create()
│ └── cancel.ts → client.routes.booking.cancel()
├── get-stats.ts → client.routes.getStats()The .generated/ Directory
Running bunx questpie generate creates .generated/ with:
index.ts — App instance and types
// Auto-generated — do not edit
import type { AppCollections, AppGlobals, AppRoutes, AppJobs } from "./module";
export type AppConfig = {
collections: AppCollections;
globals: AppGlobals;
routes: AppRoutes;
// ... auth, locales
};
export { app, createContext } from "./app";
export type { AppConfig, AppCollections, AppGlobals, AppRoutes, AppJobs };Module augmentation
The generated code augments AppContext so every handler gets typed DI:
declare global {
namespace Questpie {
interface AppContext {
db: Database;
email: MailerService<AppEmailTemplates>;
queue: QueueClient<AppJobs>;
collections: AppCollections;
globals: AppGlobals;
session: Session | null;
// ... custom services
}
}
}This is why collections, queue, email are available and typed in every handler — codegen generates the augmentation from your file structure.
The #questpie Imports
QUESTPIE uses Node.js subpath imports (the "imports" field in package.json) to wire your code to the generated output:
| Import | Resolves to | Use for |
|---|---|---|
#questpie | .generated/index.ts | app instance, AppConfig type |
#questpie/factories | .generated/factories.ts | collection(), global(), sidebar(), etc. |
Collection, global, and admin singleton files import from #questpie/factories:
import { collection } from "#questpie/factories";The generated factories file provides typed builders with all plugin extensions (e.g. .admin(), .form(), .list()) and the merged field set (builtins + module-contributed fields like richText, blocks).
Routes, jobs, and seeds import from the bare questpie package:
import { route } from "questpie";
import { job } from "questpie";
import { seed } from "questpie";By-Feature Layout
For larger projects, you can organize by feature instead of by type:
src/questpie/server/
├── features/
│ ├── blog/
│ │ ├── collections/
│ │ │ └── posts.ts
│ │ ├── routes/
│ │ │ └── publish.ts
│ │ └── jobs/
│ │ └── notify-subscribers.ts
│ └── booking/
│ ├── collections/
│ │ └── appointments.ts
│ └── routes/
│ └── create-booking.ts
├── questpie.config.ts
└── modules.tsBoth layouts can coexist. Codegen scans all configured paths.
What's Next
- Collections — Define your data models
- Fields — Field types and options
- File Convention — Deep dive into discovery mechanics