Skip to content

Project Structure

This page covers the complete folder layout, naming conventions, and file organization rules for a CleanSlice project.

Top-Level Layout

my-project/
├── api/                          # Backend (NestJS)
│   ├── src/
│   │   ├── slices/               # All features and setup modules
│   │   ├── app.module.ts         # Root module
│   │   └── main.ts               # Entry point
│   ├── prisma/
│   │   └── schema.prisma         # Database schema
│   ├── tsconfig.json
│   └── package.json

├── app/                          # Frontend (Nuxt)
│   ├── slices/                   # All features and setup modules
│   │   ├── setup/                # Infrastructure slices
│   │   └── {feature}/            # Feature slices
│   ├── nuxt.config.ts            # Root config (registers slices)
│   └── package.json

└── docker-compose.yml            # Local services (Postgres, Redis, etc.)

Backend Slice Structure

Each backend feature slice follows this layout:

api/src/slices/{entity}/
├── {entity}.module.ts                # Module definition
├── {entity}.controller.ts            # HTTP endpoints
├── domain/
│   ├── index.ts                      # Barrel exports
│   ├── {entity}.types.ts             # IEntityData, ICreateEntityData
│   ├── {entity}.gateway.ts           # Abstract gateway class
│   └── {entity}.service.ts           # Business logic (optional)
├── data/
│   ├── {entity}.gateway.ts           # Concrete gateway (Prisma)
│   └── {entity}.mapper.ts            # Data transformation
└── dtos/
    ├── index.ts                      # Barrel exports
    ├── {entity}.dto.ts               # Response DTO
    ├── create{Entity}.dto.ts         # Create request DTO
    ├── update{Entity}.dto.ts         # Update request DTO
    └── filter{Entity}.dto.ts         # Query filter DTO

Data flow through the layers

HTTP Request

Controller              validates input with DTOs

Service (optional)      orchestrates business logic

Gateway (abstract)      domain contract

Gateway (concrete)      Prisma queries

Mapper                  transforms Prisma → domain types

HTTP Response

Frontend Slice Structure

Each frontend feature slice is a Nuxt layer:

app/slices/{entity}/
├── nuxt.config.ts                    # Slice configuration + aliases
├── pages/
│   ├── {entities}.vue                # List page (plural route)
│   ├── {entities}/[id].vue           # Detail page
│   └── {entities}/create.vue         # Create page
├── components/
│   └── {entity}/                     # Component group (one level deep)
│       ├── Provider.vue              # Data fetching + bootstrapping
│       ├── Item.vue                  # Display component
│       └── Form.vue                  # Edit form
├── stores/
│   └── {entity}.ts                   # Pinia store
└── locales/
    ├── en.json                       # English translations
    └── fr.json                       # French translations

Component Nesting

Components are organized one level deep inside a named folder. Do not nest further. Each component folder should contain a Provider.vue that handles data fetching and bootstrapping.

Naming Conventions

Folders and Files

WhatConventionExample
Slice foldersingular, lowercaseuser/, project/, chat/
DTO filescamelCasecreateUser.dto.ts, filterProject.dto.ts
Type filesdot notationuser.types.ts, project.gateway.ts
Component folderscamelCaseuser/, userList/, projectCard/

TypeScript Naming

WhatConventionExample
InterfacesI prefixIUserData, ICreateUserData
Gateway interfacesI prefix + GatewayIUserGateway
EnumsTypes suffixRoleTypes, StatusTypes
Domain typesData suffixIUserData, IProjectData
Create typesICreate + DataICreateUserData

Routes

WhatConventionExample
Controller routesplural@Controller('users')
Page file namespluralpages/projects.vue
Slice folderssingularslices/user/

The Singular Rule

Slice folders are always singular (user/, not users/). Routes and page files are always plural (/users, projects.vue). This distinction is consistent throughout the framework.

Path Aliases

Backend (# alias)

Configure in api/tsconfig.json:

json
{
  "compilerOptions": {
    "paths": {
      "#": ["src/slices"],
      "#*": ["src/slices/*"]
    }
  }
}

Usage:

typescript
import { PrismaService } from '#prisma';
import { IUserGateway } from '#user/domain';
import { CreateUserDto } from '#user/dtos';

Frontend (per-slice aliases)

Each slice defines its own alias in nuxt.config.ts:

typescript
// slices/user/nuxt.config.ts
import { fileURLToPath } from 'url';
import { dirname } from 'path';

const currentDir = dirname(fileURLToPath(import.meta.url));

export default defineNuxtConfig({
  alias: {
    '#user': currentDir,
  },
});

The API setup slice exposes the generated SDK:

typescript
import { UsersService, ProjectsService } from '#api';

Module Registration

Backend

Import all slice modules in api/src/app.module.ts:

typescript
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { PrismaModule } from '#prisma';
import { UserModule } from '#user/user.module';
import { ProjectModule } from '#project/project.module';

@Module({
  imports: [
    ConfigModule.forRoot({ isGlobal: true }),
    PrismaModule,
    UserModule,
    ProjectModule,
  ],
})
export class AppModule {}

Frontend

Register slices as Nuxt layers in app/nuxt.config.ts:

typescript
export default defineNuxtConfig({
  extends: [
    // Setup slices first (order matters)
    './slices/setup/theme',
    './slices/setup/pinia',
    './slices/setup/api',
    './slices/setup/error',
    './slices/setup/i18n',
    // Feature slices
    './slices/user',
    './slices/project',
  ],
});

Auto-Imports

Nuxt auto-imports Vue APIs and composables. You should not manually import them:

typescript
// Don't do this
import { ref, computed, onMounted } from 'vue';
import { useAsyncData } from '#imports';

// Just use them directly
const count = ref(0);
const doubled = computed(() => count.value * 2);
const { data } = await useAsyncData('key', () => fetchData());

Pinia stores defined in any slice's stores/ folder are also auto-imported when the Pinia setup slice is configured.

What's Next?

Built with CleanSlice