ExisJS

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.json

File Conventions & Responsibilities

Within src/http/, each file name denotes a specific role in the HTTP lifecycle:

File PatternRoleDescription
route.ts / *.route.tsEndpoint DefinitionBinds HTTP methods (GET, POST, PUT, DELETE, PATCH) to URL paths.
schema.ts / *.schema.tsRequest ValidationDefines tex schemas for request body, query parameters, URL path parameters, and headers.
service.ts / *.service.tsBusiness LogicHandles database operations and domain logic. Injected via Dependency Injection or called as pure functions.
boundary.ts / *.boundary.tsScoped PipelineFolder-level configuration (CORS, headers, rate limits, authentication guards, and error handling) cascading down directory trees.
server.tsServer LifecycleExports onStart and onClose hooks for database connections and plugin initialization.
error.ts / *.error.tsException HandlingCentralized 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 → /users
  • src/http/users/[id]/route.ts → /users/:id
  • src/http/(admin)/dashboard/route.ts → /dashboard (parenthesized route groups do not affect the URL path)

Paradigm Examples

Functional Paradigm
src/http/users/route.ts
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
src/http/users/route.ts
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:

src/http/users/schema.ts
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

src/http/users/service.ts
export async function getUserById(id: string) {  return { id, name: 'Alice', role: 'member' }}

Class-Based (OOP) Service

src/http/users/service.ts
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

src/http/admin/boundary.ts
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

src/http/admin/boundary.ts
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:

src/http/error.ts
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:

src/cron/cleanup.ts
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 via registerDatabase.
  • src/config/env.ts: Validates environment variables at boot using tex.env:
src/config/env.ts
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

DirectoryTypeDescription
src/http/Auto-DiscoveredScanned at startup for routes, boundaries, schemas, services, and error handlers.
src/cron/Auto-DiscoveredScanned at startup for scheduled background task definitions.
src/database/ConventionRecommended folder for database connection instances, ORM models, and migrations.
src/common/ConventionRecommended folder for shared guards, filters, and cross-cutting helpers.
src/config/ConventionRecommended folder for environment schemas and static configurations.
tests/ConventionRecommended 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.