Skip to main content

Best Practices

Quick reference for structuring Navios applications.

Project Structure

Organize your application with a clear, scalable structure:

src/
├── api/ # API endpoint definitions
│ ├── user.endpoints.ts
│ ├── order.endpoints.ts
│ └── index.ts
├── modules/ # Feature modules
│ ├── user/
│ │ ├── user.module.ts
│ │ ├── user.controller.ts
│ │ ├── user.service.ts
│ │ └── user.repository.ts
│ └── order/
│ ├── order.module.ts
│ ├── order.controller.ts
│ └── order.service.ts
├── shared/ # Shared utilities
│ ├── guards/
│ ├── decorators/
│ └── utils/
├── config/ # Configuration
│ └── app.config.ts
├── app.module.ts # Root module
└── main.ts # Application entry point

API Definitions

Keep all endpoint definitions in a centralized api/ directory:

// api/user.endpoints.ts
import { builder } from '@navios/builder'
import { z } from 'zod'

const userApi = builder()

export const getUser = userApi.declareEndpoint({
method: 'GET',
url: '/users/$userId',
responseSchema: userSchema,
})

export const createUser = userApi.declareEndpoint({
method: 'POST',
url: '/users',
requestSchema: createUserSchema,
responseSchema: userSchema,
})

This allows sharing endpoint definitions between server and client for full type safety.

Module Organization

Feature Modules

Group related functionality into feature modules:

// Good - focused feature module
@Module({
controllers: [UserController],
})
export class UserModule {}

// Avoid - everything in one module
@Module({
controllers: [UserController, OrderController, ProductController, ...],
})
export class AppModule {}

Shared Modules

Create modules for cross-cutting concerns:

@Module({
controllers: [], // Provides services only
})
export class DatabaseModule {}

Key Guidelines

TopicGuidelineDetails
ControllersKeep thin, delegate to servicesControllers guide
ServicesSingle responsibilityServices guide
ErrorsUse appropriate exception typesError handling
GuardsApply at module or endpoint levelGuards guide
ConfigUse typed ConfigServiceConfiguration
LoggingInclude context, use correct levelsLogging guide
TestingUnit, integration, and E2ETesting guide

Quick Tips

Controllers: Delegate business logic to services. Controllers handle HTTP concerns only.

Services: One responsibility per service. Inject dependencies, don't instantiate them.

Endpoints: Always use @navios/builder for type-safe definitions.

Guards: Use module-level guards for broad protection, endpoint-level for specific routes.

Configuration: Use getOrThrow for required values, getOrDefault for optional.

Logging: Always include context: inject(Logger, { context: 'ServiceName' }).

Security: Validate all input with Zod schemas. Never log sensitive data.