Awesome-omni-skill backend-nodejs
Node.js/TypeScript backend expert. Handles Express/Fastify API routes, TypeScript strict mode, Prisma ORM, Zod validation, error handling, configuration management. Use when project is Node.js backend (package.json + TypeScript server).
git clone https://github.com/diegosouzapw/awesome-omni-skill
T=$(mktemp -d) && git clone --depth=1 https://github.com/diegosouzapw/awesome-omni-skill "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/development/backend-nodejs-nera0875" ~/.claude/skills/diegosouzapw-awesome-omni-skill-backend-nodejs && rm -rf "$T"
skills/development/backend-nodejs-nera0875/SKILL.mdBackend Node.js Skill - API Development Expert
Expert backend Node.js/TypeScript pour API REST + GraphQL
Inspiré de : Vercel API design, Stripe backend patterns, Prisma best practices
Scope
Chargé par: executor agent (SI projet Node.js détecté)
Détection auto:
- Fichier
existepackage.json - Fichiers
ouserver.ts
ouindex.tssrc/server.ts - Dependencies:
,express
,fastify
, etc@prisma/client
Stack supporté:
- Runtime: Node.js 18+ / Bun
- Language: TypeScript (strict mode)
- Frameworks: Express, Fastify, Hono
- Database: Prisma ORM
- Validation: Zod
- Testing: Vitest, Jest
Conventions Strictes
1. Structure Projet (Obligatoire)
backend/ ├── src/ │ ├── config/ │ │ └── env.ts # Configuration centralisée (1 seul fichier) │ ├── routes/ │ │ ├── index.ts # Router principal │ │ ├── tasks.ts │ │ └── users.ts │ ├── services/ │ │ ├── task.service.ts # Business logic │ │ └── user.service.ts │ ├── middleware/ │ │ ├── auth.ts │ │ ├── error.ts │ │ └── validation.ts │ ├── types/ │ │ └── index.ts # TypeScript types partagés │ ├── lib/ │ │ ├── prisma.ts # Prisma client singleton │ │ └── logger.ts │ └── server.ts # Entry point ├── prisma/ │ └── schema.prisma ├── package.json ├── tsconfig.json └── .env
2. Configuration (1 seul fichier - 12-Factor App)
// src/config/env.ts import { z } from 'zod' const envSchema = z.object({ NODE_ENV: z.enum(['development', 'production', 'test']).default('development'), PORT: z.string().default('3000'), DATABASE_URL: z.string(), JWT_SECRET: z.string().min(32), CORS_ORIGIN: z.string().default('http://localhost:3000'), LOG_LEVEL: z.enum(['debug', 'info', 'warn', 'error']).default('info'), }) export const env = envSchema.parse(process.env) export type Env = z.infer<typeof envSchema>
Principe: Validation config au démarrage, crash immédiat si config invalide.
3. Prisma Client Singleton (Pattern Vercel)
// src/lib/prisma.ts import { PrismaClient } from '@prisma/client' import { env } from '@/config/env' const globalForPrisma = globalThis as unknown as { prisma: PrismaClient | undefined } export const prisma = globalForPrisma.prisma ?? new PrismaClient({ log: env.NODE_ENV === 'development' ? ['query', 'error', 'warn'] : ['error'], }) if (env.NODE_ENV !== 'production') { globalForPrisma.prisma = prisma }
Interdiction: Créer plusieurs instances PrismaClient (épuise connexions DB).
4. Routes Pattern (Express)
// src/routes/tasks.ts import { Router } from 'express' import { z } from 'zod' import { taskService } from '@/services/task.service' import { validateRequest } from '@/middleware/validation' import { requireAuth } from '@/middleware/auth' const router = Router() // Zod schemas const createTaskSchema = z.object({ body: z.object({ title: z.string().min(1).max(200), description: z.string().optional(), status: z.enum(['PENDING', 'IN_PROGRESS', 'COMPLETED']).default('PENDING'), }), }) // Routes router.post( '/', requireAuth, validateRequest(createTaskSchema), async (req, res, next) => { try { const task = await taskService.createTask(req.user.id, req.body) res.status(201).json(task) } catch (error) { next(error) } } ) router.get( '/', requireAuth, async (req, res, next) => { try { const tasks = await taskService.getTasks(req.user.id) res.json(tasks) } catch (error) { next(error) } } ) router.patch( '/:id', requireAuth, validateRequest(z.object({ params: z.object({ id: z.string().cuid() }), body: createTaskSchema.shape.body.partial(), })), async (req, res, next) => { try { const task = await taskService.updateTask( req.params.id, req.user.id, req.body ) res.json(task) } catch (error) { next(error) } } ) export default router
Pattern: Routes légères → Business logic dans services.
5. Services Pattern (Singleton + Business Logic)
// src/services/task.service.ts import { prisma } from '@/lib/prisma' import { TaskStatus } from '@prisma/client' export class TaskService { async createTask( userId: string, data: { title: string; description?: string; status?: TaskStatus } ) { // Business logic validation const existingCount = await prisma.task.count({ where: { userId, status: 'PENDING' }, }) if (existingCount >= 100) { throw new Error('Maximum pending tasks limit reached') } // Create with transaction if multiple operations return prisma.task.create({ data: { ...data, userId, }, include: { user: { select: { id: true, name: true, email: true }, }, }, }) } async getTasks(userId: string) { return prisma.task.findMany({ where: { userId }, include: { user: { select: { id: true, name: true }, }, }, orderBy: { createdAt: 'desc' }, }) } async updateTask( id: string, userId: string, data: Partial<{ title: string; description?: string; status?: TaskStatus }> ) { // Check ownership const task = await prisma.task.findUnique({ where: { id }, select: { userId: true }, }) if (!task || task.userId !== userId) { throw new Error('Task not found or access denied') } return prisma.task.update({ where: { id }, data, }) } async deleteTask(id: string, userId: string) { // Check ownership const task = await prisma.task.findUnique({ where: { id }, select: { userId: true }, }) if (!task || task.userId !== userId) { throw new Error('Task not found or access denied') } return prisma.task.delete({ where: { id }, }) } } // Singleton export export const taskService = new TaskService()
Principe: 1 service = 1 ressource, toute business logic centralisée.
6. Error Handling Middleware (Standardisé)
// src/middleware/error.ts import { Request, Response, NextFunction } from 'express' import { ZodError } from 'zod' import { Prisma } from '@prisma/client' import { logger } from '@/lib/logger' export class AppError extends Error { constructor( message: string, public statusCode: number = 500, public type: string = 'server_error' ) { super(message) } } export function errorHandler( error: Error, req: Request, res: Response, next: NextFunction ) { // Zod validation error if (error instanceof ZodError) { return res.status(400).json({ message: 'Validation error', type: 'validation_error', errors: error.errors.map(e => ({ path: e.path.join('.'), message: e.message, })), }) } // Prisma errors if (error instanceof Prisma.PrismaClientKnownRequestError) { if (error.code === 'P2002') { return res.status(409).json({ message: 'Resource already exists', type: 'conflict_error', }) } if (error.code === 'P2025') { return res.status(404).json({ message: 'Resource not found', type: 'not_found_error', }) } } // App errors (custom) if (error instanceof AppError) { return res.status(error.statusCode).json({ message: error.message, type: error.type, }) } // Unknown errors logger.error('Unexpected error:', error) return res.status(500).json({ message: 'Internal server error', type: 'server_error', }) }
7. Validation Middleware (Zod)
// src/middleware/validation.ts import { Request, Response, NextFunction } from 'express' import { AnyZodObject, ZodError } from 'zod' export function validateRequest(schema: AnyZodObject) { return async (req: Request, res: Response, next: NextFunction) => { try { await schema.parseAsync({ body: req.body, query: req.query, params: req.params, }) next() } catch (error) { if (error instanceof ZodError) { return res.status(400).json({ message: 'Validation error', type: 'validation_error', errors: error.errors.map(e => ({ path: e.path.join('.'), message: e.message, })), }) } next(error) } } }
8. Server Entry Point
// src/server.ts import express from 'express' import cors from 'cors' import helmet from 'helmet' import { env } from '@/config/env' import { errorHandler } from '@/middleware/error' import { logger } from '@/lib/logger' import router from '@/routes' const app = express() // Security middleware app.use(helmet()) app.use(cors({ origin: env.CORS_ORIGIN })) app.use(express.json({ limit: '10mb' })) // Health check app.get('/health', (req, res) => { res.json({ status: 'ok', timestamp: new Date().toISOString() }) }) // API routes app.use('/api', router) // Error handler (MUST be last) app.use(errorHandler) // Start server const server = app.listen(env.PORT, () => { logger.info(`Server running on port ${env.PORT}`) logger.info(`Environment: ${env.NODE_ENV}`) }) // Graceful shutdown process.on('SIGTERM', () => { logger.info('SIGTERM received, shutting down gracefully') server.close(() => { logger.info('Server closed') process.exit(0) }) }) export default app
9. TypeScript Config (Strict Mode)
// tsconfig.json { "compilerOptions": { "target": "ES2022", "module": "ESNext", "moduleResolution": "bundler", "lib": ["ES2022"], "outDir": "./dist", "rootDir": "./src", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, "resolveJsonModule": true, "isolatedModules": true, "noUnusedLocals": true, "noUnusedParameters": true, "noImplicitReturns": true, "noFallthroughCasesInSwitch": true, "paths": { "@/*": ["./src/*"] } }, "include": ["src/**/*"], "exclude": ["node_modules", "dist"] }
Naming Conventions
Variables & Functions (camelCase)
const userId = "123" const currentUser = await getUser() async function createTask() {} async function getUserTasks() {}
Classes & Types (PascalCase)
class TaskService {} type TaskResponse = {} interface UserData {}
Files (kebab-case)
task.service.ts user.controller.ts auth.middleware.ts
Constants (SCREAMING_SNAKE_CASE)
const MAX_TASKS_PER_USER = 100 const DEFAULT_PAGE_SIZE = 20
Anti-Patterns à Éviter
❌ Multiple PrismaClient instances
// ❌ INTERDIT const prisma = new PrismaClient() // Dans chaque fichier
✅ Singleton pattern
// ✅ CORRECT import { prisma } from '@/lib/prisma'
❌ Business logic dans routes
// ❌ INTERDIT router.post('/', async (req, res) => { const task = await prisma.task.create({ data: req.body }) res.json(task) })
✅ Business logic dans services
// ✅ CORRECT router.post('/', async (req, res) => { const task = await taskService.createTask(req.user.id, req.body) res.json(task) })
❌ Pas de validation
// ❌ INTERDIT router.post('/', async (req, res) => { const task = await taskService.createTask(req.body) // Pas de validation })
✅ Zod validation middleware
// ✅ CORRECT router.post('/', validateRequest(createTaskSchema), async (req, res) => { const task = await taskService.createTask(req.body) })
❌ Errors non catchés
// ❌ INTERDIT router.get('/', async (req, res) => { const tasks = await taskService.getTasks() // Crash si erreur })
✅ Try/catch + next(error)
// ✅ CORRECT router.get('/', async (req, res, next) => { try { const tasks = await taskService.getTasks() res.json(tasks) } catch (error) { next(error) // Error handler middleware } })
Checklist Feature API
Avant créer route:
- Service existe? (anti-duplication)
- Zod schema défini?
- Auth middleware si protected?
- Error handling (try/catch + next)?
Après créer route:
- Route exportée dans
?routes/index.ts - Types TypeScript définis?
- Ownership checks (si user data)?
Exemples Complets
Exemple 1: CRUD Tasks API
Structure créée:
src/ ├── routes/tasks.ts # Routes HTTP ├── services/task.service.ts # Business logic ├── types/task.ts # TypeScript types
Workflow:
- Zod schema validation
- Route appelle service
- Service utilise Prisma
- Error handling middleware
Exemple 2: Auth Middleware
// src/middleware/auth.ts import { Request, Response, NextFunction } from 'express' import jwt from 'jsonwebtoken' import { env } from '@/config/env' import { AppError } from '@/middleware/error' declare global { namespace Express { interface Request { user: { id: string; email: string } } } } export async function requireAuth( req: Request, res: Response, next: NextFunction ) { try { const token = req.headers.authorization?.replace('Bearer ', '') if (!token) { throw new AppError('Authentication required', 401, 'auth_error') } const decoded = jwt.verify(token, env.JWT_SECRET) as { id: string email: string } req.user = decoded next() } catch (error) { if (error instanceof jwt.JsonWebTokenError) { return next(new AppError('Invalid token', 401, 'auth_error')) } next(error) } }
Workflow Executor avec Backend-Node Skill
Détection auto:
1. executor scan projet 2. Trouve package.json + src/server.ts 3. Load backend-nodejs skill (au lieu de backend Python) 4. Applique conventions Node.js/TypeScript
Feature création:
User: "Crée CRUD tasks API" executor: 1. Load skills: backend-nodejs + frontend + integration 2. Crée structure selon conventions Node.js 3. Zod validation + Prisma + Express routes 4. Services singleton pattern 5. Error handling standardisé
Principes
- TypeScript Strict Mode - Zéro
, types partoutany - Configuration Centralisée - 1 seul
(12-Factor App)env.ts - Services Singleton - 1 service par ressource, réutilisable
- Validation Zod - Toutes inputs validées
- Error Handling Standardisé - Errors structurés + types
- Prisma Best Practices - Singleton client, select optimization
- Separation of Concerns - Routes → Services → Prisma
Inspiré de:
- Vercel API Routes design
- Stripe backend architecture (services pattern)
- Prisma documentation (singleton, transactions)
- Express.js best practices
Version: 1.0.0 Created: 2025-01-10 Maintained by: executor agent (loaded si projet Node.js détecté)