Comprehensive backend development guide for Node.js/Express/TypeScript microservices.
Establish consistency and best practices for the Quantum Skincare backend API using modern Node.js/Express/TypeScript patterns with Clerk authentication, Prisma ORM, and structured error handling.
Automatically activates when working on:
routes/, delegate to controller@quantum/data-access@quantum/shared-validationrequireAuthClerk middleware for protected routesapp.tsrequireAuthClerkerrorHandler/v1HTTP Request
↓
Routes (routing only)
↓
Controllers (request handling)
↓
Services (business logic)
↓
Repositories (data access)
↓
Database (Prisma)
Key Principle: Each layer has ONE responsibility.
See architecture-overview.md for complete details.
apps/backend/src/
├── controllers/ # Request handlers (e.g., users.controller.ts)
│ └── quota/ # Feature-specific controllers
├── services/ # Business logic organized by domain
│ ├── clerk/ # Clerk user synchronization
│ ├── consent/ # Consent management
│ ├── geo/ # IP geolocation
│ ├── quota/ # Quota management
│ ├── scans/ # Scan processing
│ ├── skin-analysis/ # Skin analysis orchestration
│ ├── storage/ # S3 storage
│ └── treatment/ # Treatment recommendations
├── routes/ # Route definitions (e.g., users.ts, skin-analysis.ts)
├── middleware/ # Express middleware (auth, validation, error handling)
├── types/ # Backend-specific TypeScript types
├── utils/ # Utilities (logger, errors)
├── __tests__/ # Tests (unit + integration)
├── app.ts # Express app setup + middleware chain
└── main.ts # Server entry point + secret initialization
Naming Conventions:
kebab-case.controller.ts - users.controller.tskebab-case.service.ts - user-deletion.service.tskebab-case.ts - skin-analysis.tskebab-case.ts - auth-clerk.ts// ❌ NEVER: Business logic in routes
router.post('/submit', async (req, res) => {
// 200 lines of logic
});
// ✅ ALWAYS: Delegate to controller function
router.post('/submit', asyncHandler(submitController));
// Wraps async functions to catch errors automatically
export const getUser = asyncHandler(async (req: Request, res: Response) => {
const user = await getUserById(req.params.id);
res.json(createSuccessResponse(user, 'User retrieved'));
});
import { ValidationError, NotFoundError, UnauthorizedError } from '../utils/errors.js';
// Throw specific errors - errorHandler will format response
if (!user) {
throw new NotFoundError('User');
}
if (!isValid) {
throw new ValidationError('Invalid email format');
}
// ✅ Access environment variables directly
const port = process.env.PORT || 5000;
const clerkSecretKey = process.env.CLERK_SECRET_KEY;
// Validate critical env vars at startup (see main.ts)
if (!process.env.CLERK_SECRET_KEY) {
logger.fatal('Missing CLERK_SECRET_KEY');
process.exit(1);
}
import { validateRequest } from '../middleware/validation.js';
import { consentSchemas } from '@quantum/shared-validation';
// Use validateRequest middleware
export const acceptConsent: RequestHandler[] = [
validateRequest({ body: consentSchemas.acceptConsent }),
asyncHandler(async (req, res) => {
// req.body is now type-safe and validated
})
];
import { getUserByClerkId, upsertUserFromClerk } from '@quantum/data-access';
// ✅ Use DAOs for database access
const user = await getUserByClerkId(clerkUserId);
// ❌ Don't use Prisma directly in controllers
const user = await prisma.users.findUnique(...);
import { requireAuthClerk } from '../middleware/auth-clerk.js';
// Apply auth middleware to protected routes
router.get('/me', requireAuthClerk, getUserController);
// User payload available via getUserPayload(req)
const user = getUserPayload(req); // { id, email, tier, consent }
import { logger } from '../utils/logger.js';
// Include requestId and relevant context
logger.info({
userId: user.id,
requestId: req.requestId,
event: 'consent.accepted'
}, 'Consent accepted successfully');
logger.error({ error, userId: user.id }, 'Failed to process request');
// Express
import express, { Request, Response, NextFunction, Router, RequestHandler } from 'express';
// Error handling
import { asyncHandler } from '../middleware/error-handler.js';
import {
ValidationError,
NotFoundError,
UnauthorizedError,
ForbiddenError,
ConflictError,
QuotaExceededError
} from '../utils/errors.js';
// Response helpers
import { createSuccessResponse, createErrorResponse } from '../types/api-responses.js';
// Validation
import { validateRequest } from '../middleware/validation.js';
import { z } from 'zod';
// Database (via DAOs)
import {
getUserByClerkId,
upsertUserFromClerk,
createConsentAudit,
getPersonalInfo
} from '@quantum/data-access';
// Shared validation schemas
import { consentSchemas } from '@quantum/shared-validation';
// Shared types
import type { UserApp, ConsentProfile } from '@quantum/shared-types';
// Auth
import { requireAuthClerk, requireClerkToken } from '../middleware/auth-clerk.js';
import { getUserPayload } from '../types/express-auth.js';
import { clerkClient } from '@clerk/express';
// Logging
import { logger } from '../utils/logger.js';
| Code | Use Case |
|---|---|
| 200 | Success |
| 201 | Created |
| 400 | Bad Request |
| 401 | Unauthorized |
| 403 | Forbidden |
| 404 | Not Found |
| 500 | Server Error |
apps/backend/src/ - Quantum Skincare Backend API
routes/users.ts, routes/skin-analysis.tscontrollers/users.controller.tsservices/consent/, services/skin-analysis/middleware/auth-clerk.ts, middleware/validation.ts❌ Business logic in routes ❌ Direct Prisma access in controllers (use DAOs) ❌ Missing error handling or asyncHandler wrapper ❌ No input validation ❌ console.log instead of logger ❌ Not using custom error classes ❌ Forgetting requireAuthClerk on protected routes ❌ Accessing req.user without getUserPayload helper
| Need to... | Read this |
|---|---|
| Understand architecture | architecture-overview.md |
| Create routes/controllers | routing-and-controllers.md |
| Organize business logic | services-and-repositories.md |
| Validate input | validation-patterns.md |
| Add error tracking | sentry-and-monitoring.md |
| Create middleware | middleware-guide.md |
| Database access | database-patterns.md |
| Manage config | configuration.md |
| Handle async/errors | async-and-errors.md |
| Write tests | testing-guide.md |
| See examples | complete-examples.md |
Layered architecture, request lifecycle, separation of concerns
Route definitions, BaseController, error handling, examples
Service patterns, DI, repository pattern, caching
Zod schemas, validation, DTO pattern
Sentry init, error capture, performance monitoring
Auth, audit, error boundaries, AsyncLocalStorage
PrismaService, repositories, transactions, optimization
UnifiedConfig, environment configs, secrets
Async patterns, custom errors, asyncErrorWrapper
Unit/integration tests, mocking, coverage
Full examples, refactoring guide
Skill Status: COMPLETE ✅ Line Count: < 500 ✅ Progressive Disclosure: 11 resource files ✅