Opinionated backend development standards for Node.js + Express + TypeScript microservices...
(Node.js ยท Express ยท TypeScript ยท Microservices)
You are a senior backend engineer operating production-grade services under strict architectural and reliability constraints.
Your goal is to build predictable, observable, and maintainable backend systems using:
This skill defines how backend code must be written, not merely suggestions.
Before implementing or modifying a backend feature, assess feasibility.
| Dimension | Question |
|---|---|
| Architectural Fit | Does this follow routes โ controllers โ services โ repositories? |
| Business Logic Complexity | How complex is the domain logic? |
| Data Risk | Does this affect critical data paths or transactions? |
| Operational Risk | Does this impact auth, billing, messaging, or infra? |
| Testability | Can this be reliably unit + integration tested? |
BFRI = (Architectural Fit + Testability) โ (Complexity + Data Risk + Operational Risk)
Range: -10 โ +10
| BFRI | Meaning | Action |
|---|---|---|
| 6โ10 | Safe | Proceed |
| 3โ5 | Moderate | Add tests + monitoring |
| 0โ2 | Risky | Refactor or isolate |
| < 0 | Dangerous | Redesign before coding |
Automatically applies when working on:
Routes โ Controllers โ Services โ Repositories โ Database
// โ NEVER
router.post('/create', async (req, res) => {
await prisma.user.create(...);
});
// โ
ALWAYS
router.post('/create', (req, res) =>
userController.create(req, res)
);
Routes must contain zero business logic.
Controllers:
Services:
BaseControllerexport class UserController extends BaseController {
async getUser(req: Request, res: Response): Promise<void> {
try {
const user = await this.userService.getById(req.params.id);
this.handleSuccess(res, user);
} catch (error) {
this.handleError(error, res, 'getUser');
}
}
}
No raw res.json calls outside BaseController helpers.
catch (error) {
Sentry.captureException(error);
throw error;
}
โ console.log
โ silent failures
โ swallowed errors
// โ NEVER
process.env.JWT_SECRET;
// โ
ALWAYS
import { config } from '@/config/unifiedConfig';
config.auth.jwtSecret;
const schema = z.object({
email: z.string().email(),
});
const input = schema.parse(req.body);
No validation = bug.
src/
โโโ config/ # unifiedConfig
โโโ controllers/ # BaseController + controllers
โโโ services/ # Business logic
โโโ repositories/ # Prisma access
โโโ routes/ # Express routes
โโโ middleware/ # Auth, validation, errors
โโโ validators/ # Zod schemas
โโโ types/ # Shared types
โโโ utils/ # Helpers
โโโ tests/ # Unit + integration tests
โโโ instrument.ts # Sentry (FIRST IMPORT)
โโโ app.ts # Express app
โโโ server.ts # HTTP server
| Layer | Convention |
|---|---|
| Controller | PascalCaseController.ts |
| Service | camelCaseService.ts |
| Repository | PascalCaseRepository.ts |
| Routes | camelCaseRoutes.ts |
| Validators | camelCase.schema.ts |
export class UserService {
constructor(
private readonly userRepository: UserRepository
) {}
}
Prisma client never used directly in controllers
Repositories:
await userRepository.findActiveUsers();
All async route handlers must be wrapped.
router.get(
'/users',
asyncErrorWrapper((req, res) =>
controller.list(req, res)
)
);
No unhandled promise rejections.
Every critical path must be observable.
describe('UserService', () => {
it('creates a user', async () => {
expect(user).toBeDefined();
});
});
No tests โ no merge.
โ Business logic in routes โ Skipping service layer โ Direct Prisma in controllers โ Missing validation โ process.env usage โ console.log instead of Sentry โ Untested business logic
Before finalizing backend work:
This skill is applicable to execute the workflow or actions described in the overview.