backend-service-implement — Reference Material
Extracted reference tables, code templates, and patterns for the
backend-service-implementskill. This file is loaded bybackend-service-implementSKILL.md via inline pointers. Do not duplicate content back into SKILL.md.
Business Logic Derivation Patterns
From HTTP Status Codes:
201 Created— creation logic, uniqueness checks, default value assignment409 Conflict— duplicate detection or state conflict checking402 Payment Required— payment gateway integration429 Too Many Requests— rate limiting logic
From Request/Response Schema Differences:
- Fields in response but not in request (e.g.,
id,createdAt) — server-generated values - Fields in request but not in response (e.g.,
idempotencyKey) — processing-only inputs - Optional fields with defaults — fallback logic
From Endpoint Relationships:
- POST + POST /{id}/confirm — state machine pattern
- GET ?status=pending — filterable list with business states
- DELETE + 404 — soft or hard delete policy
From Task Breakdown:
- Acceptance criteria encode validation rules
- Implementation notes encode architectural decisions
- Cross-cutting concerns (auth, audit, notifications)
Helper Generation Patterns
6.1: Error Factory (RFC 9457)
Generate error-factory.ts with:
ProblemDetailinterface (type, title, status, detail, instance, extensions)createProblemDetail()factory functionBusinessErrorclass extending Error with ProblemDetail fields- Error type registry derived from API contract error responses:
const PROBLEM_TYPES = {
'validation-error': { status: 400, title: 'Validation Failed' },
'authentication-required': { status: 401, title: 'Authentication Required' },
'insufficient-permissions': { status: 403, title: 'Insufficient Permissions' },
'resource-not-found': { status: 404, title: 'Not Found' },
'unique-constraint-violation': { status: 409, title: 'Resource Already Exists' },
'invalid-state-transition': { status: 409, title: 'Invalid State Transition' },
'rate-limit-exceeded': { status: 429, title: 'Rate Limit Exceeded' },
// ... derived from API contract error responses
} as const; - Prisma error mapper (P2002 → 409, P2003 → 409, P2025 → 404)
- Fastify
setErrorHandlerplugin (NOT Express-style middleware)- Use
hasZodFastifySchemaValidationErrors(error)for 400 (NOTinstanceof ZodError) - Use
isResponseSerializationError(error)for 500 - Always set
Content-Type: application/problem+json
- Use
PHP Stack: Generate RFC 9457 via crell/api-problem v3.8.0
Go Stack: Generate custom ProblemDetail struct with application/problem+json
6.2: Pagination Helper
If cursor-based:
Generate pagination.ts + cursor.ts with:
CursorPaginationParamsinterface (cursor, limit, direction)CursorPaginatedResponse<T>interface (data, pagination: {hasNextPage, hasPreviousPage, startCursor, endCursor})paginateWithCursor()— Prisma cursor API withtake: limit + 1patternencodeCursor()/decodeCursor()— base64url encoding for opaque cursors- Default limit: 20, max: 100
If offset-based:
Generate pagination.ts with:
OffsetPaginationParams(page, pageSize)OffsetPaginatedResponse<T>(data, pagination with totalCount/totalPages)paginateWithOffset()— Prisma skip/take with$transactionfor count
PHP Stack: Use Laravel's built-in ->paginate() / ->cursorPaginate()
Go Stack: Generate pagination struct with sqlc query helpers
6.3: Auth Service (if applicable)
Generate auth.service.ts with jose library:
AuthTokenServiceclass:generateAccessToken(user)— 15min TTL, HS256, includes sub/role/tid claims, jti for identificationgenerateRefreshToken(user)— 7d TTL, stored in DB for revocabilityverifyAccessToken(token)— issuer/audience validation, 30s clock skew tolerancerotateRefreshToken(oldToken)— revoke old, issue new pair, family revocation on reuse detectionrevokeAllUserTokens(userId)— force logout
- Fastify auth hook plugin (decorateRequest, onRequest hook, Bearer extraction)
- Public route config support (
routeOptions.config?.public)
PHP Stack: Use Laravel Sanctum or lexik/jwt-authentication-bundle
Go Stack: Use golang-jwt/jwt/v5
6.4: Idempotency Middleware (if enabled)
Generate idempotency.ts with:
- Fastify
preHandlerhook for POST/PUT methods Idempotency-Keyheader extraction- Database-backed key storage (IdempotencyKey model)
- Same-request validation (method + path must match)
- In-flight concurrent request handling (409)
onSendhook to store response- 24h TTL with cleanup job
idempotency-replayed: trueheader on cached responses
Per-Method Implementation Patterns
CREATE operations (POST → 201):
- Business rule validation (beyond schema validation)
- Uniqueness checks (pre-check for better error messages, DB constraint as safety net)
- Default value assignment (server-generated fields)
- Transaction wrapping (if multi-step: inventory reservation, related records)
- Prisma
createwithincludefor response data - Idempotency key storage (if enabled)
- Post-transaction side effects (notifications, events) — OUTSIDE transaction
- DTO mapping: internal model → API response
READ operations (GET → 200):
- Authorization check (ownership, org-level access)
- Prisma
findUniqueOrThrowwithselect(prefer overincludefor specific fields) - Define reusable
selectobjects per use-case (list view vs detail view) - DTO mapping
LIST operations (GET → 200):
- Filter parameter extraction and validation
- Pagination (cursor or offset per Step 4 choice)
- Prisma
findManywith composable where clauses - Use
Prisma.validator<>()for type-safe query fragments - DTO mapping for each item
UPDATE operations (PATCH → 200):
- Authorization check
- Existence check (findUniqueOrThrow → 404)
- Business rule validation
- State transition validation (if status field, use state machine)
- Optimistic concurrency (if version field:
where: { id, version }, catch P2025) - Prisma
updatewithselect - DTO mapping
DELETE operations (DELETE → 204):
- Authorization check
- Existence check
- Soft delete:
update({ data: { deletedAt: new Date() } }) - Hard delete:
delete() - Cascade considerations (from data model FK constraints)
ACTION operations (POST /{id}/action → 200):
- Authorization check
- Existence check
- State transition validation
- Business logic execution (within transaction if multi-step)
- Side effects (notifications, events)
- DTO mapping
State Machine Generation
For resources with status enum + transition endpoints:
const TRANSITIONS: Record<Status, Status[]> = {
pending: ['confirmed', 'cancelled'],
confirmed: ['shipped', 'cancelled'],
shipped: ['delivered', 'returned'],
delivered: ['returned'],
cancelled: [],
returned: [],
};
function validateTransition(current: Status, next: Status): void {
if (!TRANSITIONS[current].includes(next)) {
throw new BusinessError(
'invalid-state-transition',
`Cannot transition from ${current} to ${next}`,
409,
undefined,
{ current, next, allowed: TRANSITIONS[current] }
);
}
}
Multi-Stack Service Patterns
PHP Stack (Laravel):
- Service classes with constructor injection + Eloquent models
- Form Requests for validation (
$request->validated(), never$request->all()) - API Resources for response transformation (never expose raw models)
- Eloquent scopes for composable query conditions
preventLazyLoading(),preventSilentlyDiscardingAttributes()inAppServiceProvider::boot()- RFC 9457 via
crell/api-problemv3.8.0 - Sanctum for auth (SPA cookies + API tokens)
- DB::transaction for multi-step operations
PHP Stack (Symfony):
- Service classes with autowired constructor injection
- Doctrine EntityManager for data access (Data Mapper pattern)
- DTOs with
#[MapRequestPayload]and Symfony Validator constraints - API Platform v4.x for automatic CRUD operations
- JWT via
lexik/jwt-authentication-bundlev3.2.0 with RS256
Go Stack:
- Feature-based
internal/packages (internal/user/service.go) - Constructor injection with small interfaces (1-3 methods) defined at consumer site
- Accept interfaces, return structs; wire manually in
main.go - sqlc for type-safe queries from annotated SQL
- go-playground/validator v10 for struct validation
- Custom
ProblemDetailstruct for RFC 9457 database/sqltransactions withcontext.Context
Anti-Patterns
All Stacks:
- Business logic in route handlers
- Direct ORM calls in route handlers
- Hardcoded secrets
- Missing error handling
- Side effects inside transactions
- Exposing raw ORM models in API responses
anytypes in TypeScript
Node.js:
- Multiple PrismaClient instances (use singleton)
instanceof ZodError(use v6 helpers)- Express-style error middleware (use Fastify's
setErrorHandler) - Missing
.jsextensions in ESM imports moduleResolution: "bundler"for backends
PHP:
- Fat controllers with business logic
- N+1 queries (use eager loading)
env()outside config files$request->all()(use$request->validated())
Go:
- Global database connections
- Ignoring errors
- Generic package names (
utils/) - Layer-based
internal/handlers/structure
Quality Check Checklist
Coverage:
- Every TODO stub from scaffold has a corresponding implementation
- Every API contract endpoint has service method coverage
- Every data model relationship is correctly queried
Error Handling:
- All services use RFC 9457 ProblemDetail format
- Prisma errors mapped (P2002 → 409, P2025 → 404)
- Business errors use error type registry
-
Content-Type: application/problem+jsonset on all error responses
Patterns:
- No business logic in route handlers (only in service layer)
- No direct Prisma calls in route handlers
- Service methods do not reference
request/replyobjects - Transactions keep side effects outside transaction boundary
- State machines validate transitions before applying
Security:
- Auth tokens validated on protected routes
- Authorization checks (ownership, org-level) in service methods
- No hardcoded secrets
- Refresh token rotation with family revocation (if auth generated)
Code Quality:
- TypeScript strict mode compatible (no
anytypes) - ESM imports with
.jsextensions (if Node.js + NodeNext) - Reusable select objects for Prisma queries
- Type-safe query fragments via
Prisma.validator<>()
Key Generation Rules — Node.js/TypeScript (Research-Informed)
- Service Layer: Plain exported functions importing the Prisma singleton — module caching acts as built-in singleton, making DI containers (tsyringe, inversify) unnecessary; testable via
vi.mock(); callable from CRON jobs or queue consumers outside HTTP context - Prisma Queries: Use
selectoverincludewhen possible; define reusable select objects per use-case; usePrisma.validator<>()for composable, type-safe query fragments; leverage$transactionfor multi-step operations - Error Handler: Use Fastify's
setErrorHandler(NOT Express-style middleware) — usehasZodFastifySchemaValidationErrors(error)for 400 (NOTinstanceof ZodError), useisResponseSerializationError(error)for 500; map PrismaClientKnownRequestError P2002 → 409, P2003 → 409, P2025 → 404; always setContent-Type: application/problem+json - RFC 9457 Fields:
type(URI),title,status,detail,instance; extensionerrors[]for validation details - JWT with jose: HS256, 15min access / 7d refresh, jti claims, refresh token rotation with family revocation, 30s clock skew tolerance, issuer + audience validation
- Cursor Pagination: Prisma cursor API with
take: limit + 1pattern; base64url cursor encoding; default limit 20, max 100 - Idempotency:
Idempotency-Keyheader, DB-backed storage, same-request validation, in-flight 409, 24h TTL, onSend response caching - Transactions: Interactive
$transactionwithmaxWait: 5000,timeout: 10000; side effects outside transaction; optimistic concurrency viawhere: { id, version }+ catch P2025 - Import Extensions: With
"type": "module"andmoduleResolution: "NodeNext", all imports MUST include.jsextensions - DTO Mapping: Always map internal models to API response format in service methods; never expose raw ORM models