Skip to main content
An Orionjs application follows a modular, component-based architecture that promotes clean organization and separation of concerns.

Root Directory

/
├── app/              # Main application code
├── node_modules/     # Project dependencies
├── .env.local.yml    # Environment variables for local development
├── .gitignore        # Git ignore file
├── biome.json        # Biome configuration (for linting/formatting)
├── package.json      # Project dependencies and scripts
├── tsconfig.json     # TypeScript configuration
└── pnpm-lock.yaml    # pnpm dependency lock file

App Directory

The app directory contains all the application code:
app/
├── config/           # Application configuration
├── components/       # Application components
├── env.d.ts          # Environment file created by @orion-js/env
└── index.ts          # Application entry point

Component Structure

Each component follows a consistent structure:
exampleComponent/
├── controllers/      # Controllers for different aspects of the component
│   ├── echoes/       # Event listeners
│   ├── routes/       # HTTP routes
│   ├── jobs/         # Background jobs
│   ├── modelResolvers/ # GraphQL model resolvers
│   └── resolvers/    # GraphQL resolvers container folder
│       ├── users/    # User-related resolvers
│       ├── orders/   # Order-related resolvers
│       └── products/ # Product-related resolvers
│       └── index.ts  # Exports all the resolvers in all subfolders as a single array
├── repos/            # Data repositories
├── schemas/          # Data schemas/models
├── services/         # Business logic services
├── migrations/       # Data migrations
├── index.ts          # Component definition with the controllers, repos, schemas and services
└── Component.md      # A markdown with notes for later use by other Agents
Each component controller type folder has an index.ts (for example controllers/resolvers/index.ts) that exports an array of classes:
export default []

Component Definition

The component definition index.ts is always structured like this:
import {component} from '@orion-js/components'
import resolvers from './controllers/resolvers'
import routes from './controllers/routes'
import jobs from './controllers/jobs'
import echoes from './controllers/echoes'
import modelResolvers from './controllers/modelResolvers'

export default component({
  resolvers,
  routes,
  jobs,
  echoes,
  modelResolvers,
})

Controllers

Controllers are the entry points for code execution. Keep controllers thin - they should only:
  • Handle input validation
  • Call the appropriate services
  • Transform and return the response
Different types of controllers:
  • Route controllers: Handle HTTP endpoints
  • GraphQL resolvers: Handle GraphQL operations (preferred over routes for client APIs)
  • Job controllers: Handle scheduled or on-demand tasks
  • Echo controllers: Handle event-based communication

Resolvers Folder Organization

The resolvers folder should be organized by entity and action:
controllers/
└── resolvers/
    ├── users/
    │   ├── GetUser/index.ts
    │   ├── CreateUser/index.ts
    │   └── UpdateUser/index.ts
    ├── orders/
    │   ├── GetOrder/index.ts
    │   └── CreateOrder/index.ts
    └── index.ts
The index.ts exports all resolvers as a single array:
import {GetUserResolvers} from './users/GetUser'
import {CreateUserResolvers} from './users/CreateUser'
import {UpdateUserResolvers} from './users/UpdateUser'
import {GetOrderResolvers} from './orders/GetOrder'
import {CreateOrderResolvers} from './orders/CreateOrder'

export default [
  GetUserResolvers,
  CreateUserResolvers,
  UpdateUserResolvers,
  GetOrderResolvers,
  CreateOrderResolvers
]

Configuration Structure

The config directory contains application-wide configuration:
config/
├── echoes/           # Event system configuration
├── graphql/          # GraphQL server configuration
├── health/           # Health check endpoints
├── http/             # HTTP server configuration
├── jobs/             # Background job configuration
├── options/          # Application options/settings
└── index.ts          # Configuration entry point

Application Bootstrap

The application is bootstrapped in app/index.ts:
import {startApp} from './config'
import exampleComponent from './exampleComponent'

startApp([exampleComponent])

Component.md Management

Component.md serves as a memory store - it contains essential notes that help understand the component without reading all the code. Structure: Component.md should contain organized “notes” as bullet points, where each note is 2-5 phrases describing:
  • Key business logic and data flows
  • Important architectural decisions
  • External dependencies and integrations
  • Critical implementation details
  • Known issues or limitations
  • How to check for permissions
  • Anything else important about the component
Best Practices:
  • BEFORE any task: Read the Component.md file if it exists
  • IF no Component.md exists: Create it by reading the whole component folder
  • AFTER any schema, service, or controller changes: Update the Component.md with relevant notes
  • AFTER adding new features: Document important implementation details
What to include: Only store notes valuable for understanding the component’s purpose, behavior, and important implementation details. What NOT to include: Avoid temporary information, detailed code explanations, or information easily derived from code structure.

Scripts Directory

For one-off scripts or maintenance tasks, use the temp/scripts directory:
/
├── app/
├── temp/
│   └── scripts/       # Temporary scripts for maintenance tasks
│       ├── migrate-legacy-data.ts
│       └── cleanup-orphaned-records.ts
└── ...
Scripts should:
  • Use tsx for execution: #!/usr/bin/env -S npx tsx
  • Be named according to the task they perform
  • Be self-contained and documented
Example script:
#!/usr/bin/env -S npx tsx

import {logger} from '@orion-js/logger'
import {getMongoConnection} from '@orion-js/mongodb'

async function main() {
  logger.info('Starting cleanup script')

  const connection = getMongoConnection({name: 'main'})
  await connection.connectionPromise

  // Script logic here

  logger.info('Cleanup complete')
  process.exit(0)
}

main().catch(error => {
  logger.error('Script failed', {error})
  process.exit(1)
})
Run with: npx tsx temp/scripts/cleanup-orphaned-records.ts

Code Organization Guidelines

  • File size limit: Avoid files over 200-300 lines of code. Refactor at that point.
  • Loop preferences: Prefer for of or for await of over .forEach. Use .map for transformations.
  • Mock data: Mocking data is only needed for tests, never for dev or prod environments.
  • Avoid any: Always declare types for variables and function parameters/returns.
  • Code duplication: Check for existing similar code before writing new implementations.

Basic Principles

  • Use component-based architecture
  • Encapsulate functionality in components
    • One component per main domain
    • Each component should contain controllers, schemas, services, and repositories
  • Prefer usage of resolvers instead of routes
  • Use dependency injection for service dependencies with @Inject() decorator
  • Follow the single responsibility principle for services
  • Use repositories for database operations
  • Handle validation and error cases appropriately
    • Prefer throwing ValidationError when validation of data fails
    • Prefer throwing UserError when the error is not a system error