Skip to content

Slices ​

A slice is a self-contained feature module. It owns everything related to one feature: API endpoints, UI components, state management, types, and data access. This page explains how slices are structured, how they interact, and the difference between feature slices and setup slices.

Anatomy of a Slice ​

Backend (NestJS) ​

api/src/slices/user/
├── user.module.ts                # NestJS module definition
├── user.controller.ts            # HTTP endpoints (@Controller('users'))
├── domain/
│   ├── index.ts                  # Barrel exports
│   ├── user.types.ts             # IUserData, ICreateUserData
│   ├── user.gateway.ts           # Abstract IUserGateway
│   └── user.service.ts           # Business logic (optional)
├── data/
│   ├── user.gateway.ts           # Concrete gateway (Prisma)
│   └── user.mapper.ts            # Prisma → domain type conversion
└── dtos/
    ├── index.ts                  # Barrel exports
    ├── user.dto.ts               # Response DTO
    ├── createUser.dto.ts         # Create request DTO
    └── updateUser.dto.ts         # Update request DTO

Frontend (Nuxt) ​

app/slices/user/
├── nuxt.config.ts                # Slice config with #alias
├── pages/
│   ├── users.vue                 # List page (plural route)
│   └── users/[id].vue            # Detail page
├── components/
│   └── user/                     # Component group
│       ├── Provider.vue          # Data fetching + bootstrapping
│       ├── Item.vue              # Display component
│       └── Form.vue              # Edit form
├── stores/
│   └── user.ts                   # Pinia store
└── locales/
    ├── en.json                   # English translations
    └── fr.json                   # French translations

Feature Slices vs Setup Slices ​

Feature Slices ​

Feature slices represent application functionality — user management, projects, chat, billing, etc. They contain domain logic, data access, and UI specific to that feature.

Examples: user/, project/, chat/, billing/

Setup Slices ​

Setup slices provide shared infrastructure that feature slices depend on. They have no domain logic of their own — they configure tools, libraries, and cross-cutting concerns.

Backend setup slices (api/src/slices/):

SlicePurpose
prisma/Database ORM connection
core/Shared decorators, interceptors, errors
aws/AWS service integrations
redis/Cache and session storage
health/Health check endpoint

Frontend setup slices (app/slices/setup/):

SlicePurpose
theme/Tailwind CSS, shadcn-vue components
pinia/State management configuration
api/Generated API SDK from OpenAPI spec
error/Centralized error handling
i18n/Internationalization

Slice Naming Rules ​

WhatRuleExample
Slice folderSingularuser/, project/, chat/
Controller routePlural@Controller('users')
Page filenamePluralpages/projects.vue
DTO filescamelCasecreateUser.dto.ts
Component folderscamelCaseuser/, userList/

WARNING

The singular vs plural distinction is important. Slice folders are always singular (user/, not users/). Routes and page files are always plural (/users, projects.vue).

Slice Communication ​

Slices can communicate with each other, but only through their public domain interfaces.

Allowed ​

typescript
// project.service.ts can import user domain types
import { IUserData } from '#user/domain';

Not Allowed ​

typescript
// project.service.ts cannot import user data layer
import { UserGateway } from '#user/data/user.gateway'; // wrong

Cross-Slice Dependencies ​

When one slice needs data from another, it should go through the other slice's public service or gateway interface:

typescript
// project/domain/project.service.ts
@Injectable()
export class ProjectService {
  constructor(
    @Inject('IProjectGateway')
    private readonly projectGateway: IProjectGateway,
    private readonly userService: UserService,  // Import the service
  ) {}

  async getProjectWithOwner(id: string) {
    const project = await this.projectGateway.findById(id);
    const owner = await this.userService.findById(project.ownerId);
    return { ...project, owner };
  }
}

Registering Slices ​

Backend ​

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

typescript
@Module({
  imports: [
    ConfigModule.forRoot({ isGlobal: true }),
    // Setup slices
    PrismaModule,
    CoreModule,
    // Feature slices
    UserModule,
    ProjectModule,
  ],
})
export class AppModule {}

Frontend ​

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

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

What's Next? ​

Built with CleanSlice