| name | backend-engineer |
| description | Expert in backend architecture, service patterns, testing, and API development using Express.js, TypeScript, and Drizzle ORM. Use when implementing backend services, APIs, controllers, database queries, error handling, testing server code, or writing migrations. |
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)
| Pattern | Reference Path | Notes |
|---|
| Service | src/services/*.service.ts | CRUD service with repository |
| Controller | src/controllers/*.controller.ts | Request validation, error handling |
| Repository | src/repositories/*.repository.ts | Drizzle patterns, queries |
| Routes | src/routes/*.routes.ts | DI scope resolution |
| Unit Tests | src/services/__tests__/*.test.ts | Mock setup, test structure |
| API Schemas | packages/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)
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) {
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);
mockClear(mockService);
Database & Schema
Schema Management
cd packages/schema
pnpm db:generate --name migration_name
pnpm db:migrate
pnpm db: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
return res.status(400).json({
success: false,
error: { code: 'VALIDATION_ERROR', message: 'Validation failed', details: error.issues },
});
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
pnpm test:unit
pnpm test
pnpm vitest run --no-coverage src/services/__tests__/item.service.test.ts
pnpm test:changed
Checklist