ugudlado avatar

backend-engineer

Expert in Template Project backend architecture, service patterns, testing, and API development usin

提供方 ugudlado|开源

Backend Engineer

Expert knowledge of backend patterns and architecture for Express.js APIs.

Core Architecture

Technology Stack

  • Runtime: Node.js with TypeScript
  • Framework: Express.js
  • Database: PostgreSQL with Drizzle ORM
  • DI Container: Awilix (Proxy injection mode)
  • Testing: Vitest with vitest-mock-extended
  • Validation: Zod schemas (CRITICAL: Use across ALL layers)

Service Layer Architecture

HTTP Request -> Routes -> Middleware -> Controller -> Service -> Repository -> Database
                                            |           |           |
                                       Validation  Business    Drizzle
                                       & Mapping     Logic        ORM

Pattern References (ALWAYS CHECK THESE FIRST)

PatternReference PathNotes
Servicesrc/services/*.service.tsCRUD service with repository
Controllersrc/controllers/*.controller.tsRequest validation, error handling
Repositorysrc/repositories/*.repository.tsDrizzle patterns, queries
Routessrc/routes/*.routes.tsDI scope resolution
Unit Testssrc/services/__tests__/*.test.tsMock setup, test structure
API Schemaspackages/schema/src/api/Zod validation schemas

Zod Validation Across All Layers (CRITICAL)

Every data boundary must have Zod validation. Never use magic strings — use enums/constants.

Layer-by-Layer Validation

1. Controller Layer (Request Validation)

// Schemas defined in packages/schema/src/api/
const CreateItemRequestSchema = z.object({
  title: z.string().min(1).max(200),
  description: z.string().optional(),
});

async createItem(req: Request, res: Response) {
  const validatedData = CreateItemRequestSchema.parse(req.body);
  const result = await this.itemService.create(validatedData);
  return res.status(201).json({ success: true, data: result });
}

2. Service Layer (Business Logic)

async create(input: CreateItemInput) {
  // Business rules here
  return await this.repository.create(input);
}

3. Repository Layer (Database Operations)

async create(data: NewItem) {
  const [item] = await this.db
    .insert(items)
    .values(data)
    .returning();
  return item;
}

Shared Schema Location

packages/schema/src/
├── api/                # API Contracts (ALWAYS USE)
│   ├── items.ts        # Request/response schemas
│   ├── common.ts       # Shared schemas (pagination, etc.)
│   └── index.ts        # Re-exports
├── schema.ts           # Drizzle database schema
└── types.ts            # Database type exports

CRITICAL: All API request/response contracts MUST be in packages/schema/src/api/. Never create validation schemas in server/src/.

Zod with Drizzle ORM

import { createInsertSchema, createSelectSchema } from 'drizzle-zod';
import { items } from '@app/schema';

export const insertItemSchema = createInsertSchema(items);
export const createItemSchema = insertItemSchema
  .extend({ title: z.string().min(1).max(200) })
  .omit({ id: true, createdAt: true });

Unit Testing Patterns

Test Structure (TDD Approach)

import { describe, it, expect, beforeEach, vi } from 'vitest';
import { mock } from 'vitest-mock-extended';

describe('ItemService', () => {
  let service: ItemService;
  let mockRepo: MockType<ItemRepository>;

  beforeEach(() => {
    vi.clearAllMocks();
    mockRepo = mock<ItemRepository>();
    service = new ItemService(mockRepo);
  });

  describe('create', () => {
    it('should create item successfully', async () => {
      const input = { title: 'Test' };
      const expected = { id: '1', ...input };
      mockRepo.create.mockResolvedValue(expected);

      const result = await service.create(input);

      expect(result).toEqual(expected);
      expect(mockRepo.create).toHaveBeenCalledWith(input);
    });
  });
});

Common Mock Patterns

mockService.method.mockResolvedValue(result);
mockService.method.mockImplementation(async (id) => id === 'valid' ? data : null);
mockReset(mockService);  // Clear all mock data
mockClear(mockService);  // Clear call history only

Database & Schema

Schema Management

cd packages/schema
pnpm db:generate --name migration_name  # Generate migration
pnpm db:migrate                         # Run migrations
pnpm db:studio                          # Open Drizzle Studio

Drizzle ORM Patterns

class ItemRepository {
  constructor(private db: NodePgDatabase) {}

  async findById(id: string) {
    return this.db.query.items.findFirst({
      where: eq(items.id, id),
    });
  }

  async findAll() {
    return this.db.select().from(items).orderBy(desc(items.createdAt));
  }
}

Error Handling

// Standardized error response
return res.status(400).json({
  success: false,
  error: { code: 'VALIDATION_ERROR', message: 'Validation failed', details: error.issues },
});

// Success response
return res.status(200).json({ success: true, data: result });

DI Container Tokens

CONTAINER_TOKENS.DATABASE;
CONTAINER_TOKENS.LOGGER;
CONTAINER_TOKENS.ITEM_SERVICE;
CONTAINER_TOKENS.ITEM_CONTROLLER;

Common Commands

pnpm dev                    # Start dev server
pnpm test:unit              # Run unit tests (fast)
pnpm test                   # All tests including integration
pnpm vitest run --no-coverage src/services/__tests__/item.service.test.ts  # Specific file
pnpm test:changed           # Test only changed packages

Checklist

  • Check pattern references before implementing
  • Zod validation at ALL boundaries
  • No magic strings — use enums/constants
  • API schemas in packages/schema/src/api/
  • Unit tests with vitest-mock-extended
  • Test coverage >= 80%
  • Standardized error responses
backend-engineer - 适用于 Claude Code 与 Cursor 的 AI 智能体 Skill | Agent Skills