Project Structure
ExisJS enforces a convention-based project structure located inside the src/ directory. File and folder locations declare their roles automatically, removing the need for manual routing configuration or boilerplate router files.
Standard Directory Layout
my-api/├── .agents/│ └── rules/│ └── AGENTS.md # LLM architectural rules and coding standards├── .exis/ # Framework cache and compiled route manifests (git-ignored)├── src/│ ├── http/ # File-system routing and HTTP request pipeline (auto-discovered)│ │ ├── server.ts # Server lifecycle hooks (onStart, onClose)│ │ ├── boundary.ts # Root boundary (CORS, security headers, global error handling)│ │ ├── route.ts # Root route handler (GET /)│ │ ├── error.ts # Global exception and validation error handler (onError)│ │ ├── health/│ │ │ └── route.ts # GET /health endpoint│ │ ├── users/│ │ │ ├── route.ts # Route endpoints for /users│ │ │ ├── schema.ts # Input validation schemas (tex.*)│ │ │ └── service.ts # Feature business logic and data access│ │ └── admin/│ │ ├── boundary.ts # Scoped boundary applying to /admin and nested routes│ │ └── posts/│ │ ├── route.ts # Route endpoints for /admin/posts│ │ ├── schema.ts # Validation schemas for /admin/posts│ │ └── service.ts # Service logic for /admin/posts│ ├── cron/ # Scheduled background tasks (auto-discovered)│ │ └── cleanup.ts # Cron task definitions│ ├── database/ # Persistent storage configuration and lifecycle│ │ ├── db.ts # Database connection client and lifecycle registration│ │ └── models/ # Database models, schemas, and entity definitions│ ├── common/ # Shared cross-cutting components│ │ └── guards/│ │ └── auth.guard.ts # Reusable route guards│ └── config/│ └── env.ts # Environment variable validation schema (tex.env)├── tests/ # Automated test suites (*.test.ts, *.e2e-spec.ts)├── .env # Local environment variables├── .gitignore├── .prettierrc├── eslint.config.mjs # ESLint flat configuration (optional)├── exis.config.ts # Application configuration file├── package.json└── tsconfig.jsonFile Conventions & Responsibilities
Within src/http/, each file name denotes a specific role in the HTTP lifecycle:
| File Pattern | Role | Description |
|---|---|---|
route.ts / *.route.ts | Endpoint Definition | Binds HTTP methods (GET, POST, PUT, DELETE, PATCH) to URL paths. |
schema.ts / *.schema.ts | Request Validation | Defines tex schemas for request body, query parameters, URL path parameters, and headers. |
service.ts / *.service.ts | Business Logic | Handles database operations and domain logic. Injected via Dependency Injection or called as pure functions. |
boundary.ts / *.boundary.ts | Scoped Pipeline | Folder-level configuration (CORS, headers, rate limits, authentication guards, and error handling) cascading down directory trees. |
server.ts | Server Lifecycle | Exports onStart and onClose hooks for database connections and plugin initialization. |
error.ts / *.error.ts | Exception Handling | Centralized error interceptor exporting onError(err, req, res). |
Directory Details
1. src/http/ (File-System Routing)
Every folder inside src/http/ maps directly to an HTTP endpoint path:
src/http/route.ts→/src/http/users/route.ts→/userssrc/http/users/[id]/route.ts→/users/:idsrc/http/(admin)/dashboard/route.ts→/dashboard(parenthesized route groups do not affect the URL path)
Paradigm Examples
Functional Paradigm
import { controller, route } from 'exisjs/router'import { userParamsSchema } from './schema'import { getUserById } from './service'
export default controller({ getUser: route.get('/:id', { summary: 'Retrieve user by ID', params: userParamsSchema, handle: async ({ params }) => { return getUserById(params.id) }, }),})Class-Based (OOP) Paradigm
import { Controller, Get, Param, ApiOperation } from 'exisjs/decorators'import { userParamsSchema } from './schema'import { Inject } from 'exisjs/di'import { UserService } from './service'
@Controller()export default class UsersController { constructor( @Inject(UserService) private readonly userService: UserService ) {}
@Get('/:id', { params: userParamsSchema }) @ApiOperation({ summary: 'Retrieve user by ID' }) async getUser(@Param('id') id: string) { return this.userService.getUserById(id) }}2. src/http/**/schema.ts (Validation Schemas)
Define request payload and parameter schemas using the tex schema builder. Schemas defined here provide compile-time type inference and drive the OpenAPI documentation generator:
import { tex, type ResolveSchema } from 'exisjs/validator'
export const userParamsSchema = tex.object({ id: tex.string({ min: 1 }),})
export const createUserSchema = tex.object({ name: tex.string({ min: 2 }), email: tex.email(), role: tex.enum(['admin', 'member'], { default: 'member' }),})
export type CreateUserDto = ResolveSchema<typeof createUserSchema>export type UserParamsDto = ResolveSchema<typeof userParamsSchema>3. src/http/**/service.ts (Business Logic)
Isolate database interactions and business logic from HTTP transport concerns:
Functional Service
export async function getUserById(id: string) { return { id, name: 'Alice', role: 'member' }}Class-Based (OOP) Service
import { Injectable } from 'exisjs/decorators'
@Injectable()export class UserService { async getUserById(id: string) { return { id, name: 'Alice', role: 'member' } }}4. src/http/**/boundary.ts (Hierarchical Boundaries)
A boundary.ts file applies scoped security guards, CORS settings, custom headers, and error handling to all routes within its directory and subdirectories:
Functional Boundary
import { defineBoundary } from 'exisjs/router'import type { Request, Response, Next } from 'exisjs/router'
export const config = defineBoundary({ cors: { origin: ['https://admin.example.com'], credentials: true }, headers: { 'X-Admin-Scope': 'active' }, exclude: ['/admin/health'],
beforeHandle(req: Request, res: Response) { if (req.headers['x-admin-key'] !== process.env.ADMIN_KEY) { res.status(403).json({ error: 'Unauthorized admin access' }) return false } },})
export function logAdminAccess(req: Request, res: Response, next: Next) { console.log(`[Admin Access] ${req.method} ${req.path}`) next()}Class-Based (OOP) Boundary
import { Boundary } from 'exisjs/decorators'import type { Request, Response, Next } from 'exisjs/router'
@Boundary({ cors: { origin: ['https://admin.example.com'], credentials: true }, headers: { 'X-Admin-Scope': 'active' }, exclude: ['/admin/health'],})export default class AdminBoundary { beforeHandle(req: Request, res: Response) { if (req.headers['x-admin-key'] !== process.env.ADMIN_KEY) { res.status(403).json({ error: 'Unauthorized admin access' }) return false } }
logAdminAccess(req: Request, res: Response, next: Next) { console.log(`[Admin Access] ${req.method} ${req.path}`) next() }}5. src/http/error.ts (Global Error Handler)
ExisJS discovers src/http/error.ts to format application errors and validation failures consistently across all endpoints:
import type { Request, Response } from 'exisjs'
export function onError(err: any, req: Request, res: Response) { const status = err.statusCode || err.status || 500
res.status(status).json({ success: false, error: { message: err.message || 'Internal Server Error', code: err.code || 'INTERNAL_SERVER_ERROR', path: req.path, }, })}6. src/cron/ (Scheduled Background Tasks)
Files placed inside src/cron/ are automatically mounted by the scheduler during server boot:
import { cron } from 'exisjs/cron'
export default cron('0 0 * * *', { name: 'daily-database-cleanup', run: async () => { console.log('[Cron] Executing nightly database maintenance...') },})7. src/database/ & src/config/
src/database/db.ts: Contains database connections and lifecycle registration viaregisterDatabase.src/config/env.ts: Validates environment variables at boot usingtex.env:
import { tex } from 'exisjs/validator'
export const env = tex .env({ PORT: tex.number({ default: 4000 }), NODE_ENV: tex.enum(['development', 'production', 'test'] as const, { default: 'development', }), CORS_ORIGIN: tex.string({ default: '*' }), }) .parse(process.env)Auto-Discovered vs Convention Directories
| Directory | Type | Description |
|---|---|---|
src/http/ | Auto-Discovered | Scanned at startup for routes, boundaries, schemas, services, and error handlers. |
src/cron/ | Auto-Discovered | Scanned at startup for scheduled background task definitions. |
src/database/ | Convention | Recommended folder for database connection instances, ORM models, and migrations. |
src/common/ | Convention | Recommended folder for shared guards, filters, and cross-cutting helpers. |
src/config/ | Convention | Recommended folder for environment schemas and static configurations. |
tests/ | Convention | Recommended folder for integration and end-to-end test suites. |
Next Steps
- Routing & Endpoints: Detailed routing guide covering route methods, parameter extraction, and route grouping.
- Validation Guide: Comprehensive reference for building schemas with
tex. - Boundaries: Scoped middleware execution, CORS cascades, and error boundary wrappers.
- Configuration: Complete options reference for
exis.config.ts.