Dwex Logo

Project Structure

Understand how to organize your Dwex application

A typical Dwex application follows this structure:

my-app/
├── src/
│   ├── modules/
│   │   ├── users/
│   │   │   ├── users.controller.ts
│   │   │   ├── users.service.ts
│   │   │   ├── users.module.ts
│   │   │   └── dto/
│   │   │       ├── create-user.dto.ts
│   │   │       └── update-user.dto.ts
│   │   ├── auth/
│   │   │   ├── auth.controller.ts
│   │   │   ├── auth.service.ts
│   │   │   ├── auth.module.ts
│   │   │   └── guards/
│   │   │       └── auth.guard.ts
│   │   └── database/
│   │       ├── database.service.ts
│   │       └── database.module.ts
│   ├── common/
│   │   ├── filters/
│   │   ├── interceptors/
│   │   ├── middleware/
│   │   └── decorators/
│   ├── config/
│   │   └── app.config.ts
│   ├── app.module.ts
│   └── main.ts
├── package.json
├── tsconfig.json
└── bun.lockb

Directory Breakdown

src/main.ts

The entry point of your application. Bootstraps the app and starts the server.

import "reflect-metadata";
import { DwexFactory } from "@dwex/core";
import { AppModule } from "./app.module";

async function bootstrap() {
  const app = await DwexFactory.create(AppModule);
  await app.listen(3000);
}

bootstrap();

src/app.module.ts

The root module that imports all feature modules.

import { Module } from "@dwex/core";
import { LoggerModule } from "@dwex/logger";
import { UserModule } from "./modules/users/users.module";
import { AuthModule } from "./modules/auth/auth.module";
import { DatabaseModule } from "./modules/database/database.module";

@Module({
  imports: [LoggerModule, DatabaseModule, UserModule, AuthModule],
})
export class AppModule {}

src/modules/

Feature modules organized by domain. Each module contains:

  • Controller: Handles HTTP requests
  • Service: Contains business logic
  • Module: Declares module dependencies
  • DTOs: Data transfer objects for validation
  • Guards/Interceptors: Module-specific middleware

Example: Users Module

// users.module.ts
@Module({
  imports: [DatabaseModule],
  controllers: [UserController],
  providers: [UserService],
  exports: [UserService],
})
export class UserModule {}
// users.controller.ts
@Controller("users")
export class UserController {
  constructor(private readonly userService: UserService) {}

  @Get()
  findAll() {
    return this.userService.findAll();
  }
}
// users.service.ts
@Injectable()
export class UserService {
  constructor(private readonly db: DatabaseService) {}

  async findAll() {
    return this.db.users.findMany();
  }
}

src/common/

Shared utilities used across modules:

  • filters/: Exception filters
  • interceptors/: Response transformers
  • middleware/: Custom middleware functions
  • decorators/: Custom parameter/method decorators

Example: Logging Interceptor

// common/interceptors/logging.interceptor.ts
@Injectable()
export class LoggingInterceptor implements DwexInterceptor {
  async intercept(context: ExecutionContext, next: () => Promise<any>) {
    const start = Date.now();
    const result = await next();
    const duration = Date.now() - start;
    console.log(`Request took ${duration}ms`);
    return result;
  }
}

src/config/

Configuration files for environment variables, database connections, etc.

// config/app.config.ts
export const appConfig = {
  port: process.env.PORT || 3000,
  database: {
    url: process.env.DATABASE_URL,
  },
  jwt: {
    secret: process.env.JWT_SECRET,
    expiresIn: "1h",
  },
};

Module Organization Patterns

Organize by feature/domain:

src/modules/
├── users/
├── posts/
├── comments/
└── auth/

Each module is self-contained with its controller, service, and module file.

Layer-First

Organize by technical layer:

src/
├── controllers/
├── services/
├── repositories/
└── models/

This works for smaller apps but becomes harder to maintain as the app grows.

Best Practices

1. One Module Per Feature

Each feature should have its own module. This promotes:

  • Encapsulation: Module internals are hidden
  • Reusability: Modules can be imported elsewhere
  • Testability: Easy to test in isolation

2. Shared Module

Create a SharedModule for commonly used providers:

@Global()
@Module({
  providers: [ConfigService, CacheService],
  exports: [ConfigService, CacheService],
})
export class SharedModule {}

3. DTOs for Validation

Use Data Transfer Objects to define expected request shapes:

// dto/create-user.dto.ts
export interface CreateUserDto {
  email: string;
  password: string;
  name: string;
}

4. Barrel Exports

Use index.ts files to simplify imports:

// modules/users/index.ts
export * from "./users.controller";
export * from "./users.service";
export * from "./users.module";

Now you can import as:

import { UserModule, UserService } from "./modules/users";

5. Environment-Based Configuration

Never hardcode configuration. Use environment variables:

const config = {
  database: {
    host: Bun.env.DB_HOST,
    port: Number(Bun.env.DB_PORT),
  },
};

Monorepo Structure (Nx)

For larger projects, Dwex works great with Nx workspaces:

my-workspace/
├── apps/
│   ├── api/
│   └── admin-api/
├── packages/
│   ├── shared/
│   ├── database/
│   └── auth/
└── nx.json

This allows sharing code between multiple applications.

Next Steps