droplinked-backend

Code style, architecture patterns, and development guide for the Droplinked e-commerce backend. NestJS + Prisma (MongoDB) with strict layered architecture: Controller to Service Facade to UseCase to Repository. Use this skill when: (1) Creating new features, modules, or endpoints in the Droplinked backend (2) Refactoring existing code to follow project patterns (3) Writing tests for use cases and services (4) Reviewing code for architectural compliance (5) Understanding the data model (shops, products, orders, carts, merchants, customers) (6) Implementing cross-module communication (7) Adding event-driven side effects (8) Working with Prisma and MongoDB in this codebase Triggers: droplinked, nest module, usecase, service facade, repository pattern, cart, order, shop, product, merchant

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "droplinked-backend" with this command: npx skills add erfanabedinpour/droplinked-skills/erfanabedinpour-droplinked-skills-droplinked-backend

Droplinked Backend Development Guide

Architecture Overview

Strict layered architecture with clear separation of concerns:

Controller -> Service Facade -> UseCase -> Repository
LayerRoleRules
ControllerHTTP onlyTransform params, call service. No logic. Use @CurrentUser(), IsObjectIdPipe
Service FacadeThin orchestrationGet UseCase via ModuleRef, call execute(). No business logic
UseCaseAll business logicExtend UseCase<T,R>, implement validate() + doExecute()
RepositoryData access onlyExtend Repository<T>, use Prisma. Semantic methods like findCartForCheckout()

Module Structure

All file names in kebab-case:

src/modules/<module-name>/
├── controllers/          # HTTP Controllers
│   └── <module>.controller.ts
├── services/             # Service Facades
│   └── <module>.service.ts
├── use-cases/            # Business Logic (one file per operation)
│   ├── create-order.use-case.ts
│   └── get-order.use-case.ts
├── repositories/         # Data Access Layer
│   └── <entity>.repository.ts
├── dtos/                 # Validation with class-validator
│   ├── create-order.dto.ts
│   └── order-response.dto.ts
├── events/               # EventEmitter definitions
│   └── order-created.event.ts
├── listeners/            # @OnEvent handlers
│   └── order.listener.ts
├── <module>.module.ts
└── docs.md               # UseCase documentation

UseCase Implementation Pattern

Extend UseCase<TRequest, TResponse> from src/common/services/use-case.base.ts:

@Injectable()
export class CreateOrderUseCase extends UseCase<CreateOrderRequest, CreateOrderResponse> {
  validate(request: CreateOrderRequest): void {
    if (!isValidObjectId(request.cartId))
      throw new BadRequestException('Invalid cart ID');
  }

  async doExecute(request: CreateOrderRequest): Promise<CreateOrderResponse> {
    // 1. Fetch and validate context
    const context = await this.validateAndFetchContext(request);

    // 2. Perform core business logic
    const result = this.performCoreLogic(context);

    // 3. Persist changes
    await this.persistChanges(result);

    // 4. Return final result
    return this.fetchFinalResult(result.id);
  }

  // Use interfaces for data between private methods
  private async validateAndFetchContext(req: Request): Promise<ExecutionContext> { ... }
  private performCoreLogic(ctx: ExecutionContext): CalculationResult { ... }
}

Key rules:

  • doExecute reads like a table of contents, not the implementation
  • Use numbered comments for step-by-step flow
  • Define interfaces for data passed between private methods
  • Never call this.prisma directly in doExecute; use semantic private methods

Cross-Module Data Access

CRITICAL: Never query another module's tables directly.

// WRONG: Direct Prisma access to another module's table
const user = await this.prisma.user.findUnique({ where: { id } });

// CORRECT: Use the owning module's service
const user = await this.userService.findUserById(id);

Each module owns its tables exclusively. Cross-module data flows through Service Facades.

Validation Strategy

Layer 1: Syntactic (DTOs)

All fields require @Is... decorators from class-validator:

export class CreateOrderDto {
  @IsString()
  @IsNotEmpty()
  cartId: string;

  @IsOptional()
  @IsString()
  note?: string;
}

Layer 2: Controller Params

Use IsObjectIdPipe for MongoDB ObjectIds:

@Get(':id')
findOne(@Param('id', IsObjectIdPipe) id: string) { ... }

Layer 3: Semantic (UseCase)

Business rules checked in validate() or early in doExecute():

private async validateBusinessRules(cart: CartV2, product: Product) {
  if (cart.shopId !== product.shopId) {
    throw new BadRequestException('Product does not belong to this shop');
  }
  if (product.inventory < 1) {
    throw new BadRequestException('Product out of stock');
  }
}

Event-Driven Side Effects

Use EventEmitter2 for non-blocking operations (emails, analytics, webhooks):

// Publisher (in UseCase)
this.eventEmitter.emit('order.created', new OrderCreatedEvent(order));

// Subscriber (in listeners/)
@OnEvent('order.created')
async handleOrderCreated(payload: OrderCreatedEvent) {
  await this.emailService.sendReceipt(payload.order.email);
}

When to use events:

  • Sending notifications (email, SMS)
  • Updating analytics/logs
  • Syncing with external systems (webhooks)

When NOT to use events:

  • Core logic requiring strict consistency (use direct calls)

Database Conventions (Prisma + MongoDB)

  • Schema at prisma/schema.prisma
  • Use select to fetch only needed fields
  • Use Promise.all for independent parallel queries
  • Use findUnique over findFirst for indexed lookups
  • Semantic repository methods: findOrderWithRelations(), not findOne()
// Good: Parallel independent queries
const [user, cart] = await Promise.all([
  this.fetchUser(userId),
  this.fetchCart(cartId),
]);

// Good: Select only needed fields
this.prisma.product.findUnique({
  where: { id },
  select: { id: true, price: true },
});

Quick Commands

npm run db:generate     # Prisma generate + Swagger docs
npm run start:dev       # Development with watch
npm run test:e2e        # End-to-end tests
npm run docs:generate   # Generate unified Swagger

Reference Files

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Coding

github-tools

Interact with GitHub using the `gh` CLI. Use `gh issue`, `gh pr`, `gh run`, and `gh api` for issues, PRs, CI runs, and advanced queries.

Archived SourceRecently Updated
Coding

openclaw-version-monitor

监控 OpenClaw GitHub 版本更新,获取最新版本发布说明,翻译成中文, 并推送到 Telegram 和 Feishu。用于:(1) 定时检查版本更新 (2) 推送版本更新通知 (3) 生成中文版发布说明

Archived SourceRecently Updated
Coding

ask-claude

Delegate a task to Claude Code CLI and immediately report the result back in chat. Supports persistent sessions with full context memory. Safe execution: no data exfiltration, no external calls, file operations confined to workspace. Use when the user asks to run Claude, delegate a coding task, continue a previous Claude session, or any task benefiting from Claude Code's tools (file editing, code analysis, bash, etc.).

Archived SourceRecently Updated
Coding

ai-dating

This skill enables dating and matchmaking workflows. Use it when a user asks to make friends, find a partner, run matchmaking, or provide dating preferences/profile updates. The skill should execute `dating-cli` commands to complete profile setup, task creation/update, match checking, contact reveal, and review.

Archived SourceRecently Updated