backend-patterns
Patrones de arquitectura backend, diseño de API, optimización de base de datos y buenas prácticas del lado del servidor para Node.js, Express y rutas API de Next.js.
What this skill does
# Patrones de Desarrollo Backend
Patrones de arquitectura backend y buenas prácticas para aplicaciones del lado del servidor escalables.
## Cuándo Activar
- Diseñar endpoints de API REST o GraphQL
- Implementar capas de repositorio, servicio o controlador
- Optimizar consultas de base de datos (N+1, indexación, connection pooling)
- Agregar caché (Redis, en memoria, headers de caché HTTP)
- Configurar trabajos en segundo plano o procesamiento asíncrono
- Estructurar manejo de errores y validación para APIs
- Construir middleware (auth, logging, rate limiting)
## Patrones de Diseño de API
### Estructura de API RESTful
```typescript
// PASS: URLs basadas en recursos
GET /api/markets # Listar recursos
GET /api/markets/:id # Obtener recurso individual
POST /api/markets # Crear recurso
PUT /api/markets/:id # Reemplazar recurso
PATCH /api/markets/:id # Actualizar recurso
DELETE /api/markets/:id # Eliminar recurso
// PASS: Parámetros de consulta para filtrado, ordenamiento, paginación
GET /api/markets?status=active&sort=volume&limit=20&offset=0
```
### Patrón Repository
```typescript
// Abstraer la lógica de acceso a datos
interface MarketRepository {
findAll(filters?: MarketFilters): Promise<Market[]>
findById(id: string): Promise<Market | null>
create(data: CreateMarketDto): Promise<Market>
update(id: string, data: UpdateMarketDto): Promise<Market>
delete(id: string): Promise<void>
}
class SupabaseMarketRepository implements MarketRepository {
async findAll(filters?: MarketFilters): Promise<Market[]> {
let query = supabase.from('markets').select('*')
if (filters?.status) {
query = query.eq('status', filters.status)
}
if (filters?.limit) {
query = query.limit(filters.limit)
}
const { data, error } = await query
if (error) throw new Error(error.message)
return data
}
// Otros métodos...
}
```
### Patrón de Capa de Servicio
```typescript
// Lógica de negocio separada del acceso a datos
class MarketService {
constructor(private marketRepo: MarketRepository) {}
async searchMarkets(query: string, limit: number = 10): Promise<Market[]> {
// Lógica de negocio
const embedding = await generateEmbedding(query)
const results = await this.vectorSearch(embedding, limit)
// Obtener datos completos
const markets = await this.marketRepo.findByIds(results.map(r => r.id))
// Ordenar por similitud
return markets.sort((a, b) => {
const scoreA = results.find(r => r.id === a.id)?.score || 0
const scoreB = results.find(r => r.id === b.id)?.score || 0
return scoreA - scoreB
})
}
private async vectorSearch(embedding: number[], limit: number) {
// Implementación de búsqueda vectorial
}
}
```
### Patrón Middleware
```typescript
// Pipeline de procesamiento de request/response
export function withAuth(handler: NextApiHandler): NextApiHandler {
return async (req, res) => {
const token = req.headers.authorization?.replace('Bearer ', '')
if (!token) {
return res.status(401).json({ error: 'Unauthorized' })
}
try {
const user = await verifyToken(token)
req.user = user
return handler(req, res)
} catch (error) {
return res.status(401).json({ error: 'Invalid token' })
}
}
}
// Uso
export default withAuth(async (req, res) => {
// El handler tiene acceso a req.user
})
```
## Patrones de Base de Datos
### Optimización de Consultas
```typescript
// PASS: BIEN: Seleccionar solo las columnas necesarias
const { data } = await supabase
.from('markets')
.select('id, name, status, volume')
.eq('status', 'active')
.order('volume', { ascending: false })
.limit(10)
// FAIL: MAL: Seleccionar todo
const { data } = await supabase
.from('markets')
.select('*')
```
### Prevención de Problema N+1
```typescript
// FAIL: MAL: Problema de consulta N+1
const markets = await getMarkets()
for (const market of markets) {
market.creator = await getUser(market.creator_id) // N consultas
}
// PASS: BIEN: Obtención en lote
const markets = await getMarkets()
const creatorIds = markets.map(m => m.creator_id)
const creators = await getUsers(creatorIds) // 1 consulta
const creatorMap = new Map(creators.map(c => [c.id, c]))
markets.forEach(market => {
market.creator = creatorMap.get(market.creator_id)
})
```
### Patrón de Transacción
```typescript
async function createMarketWithPosition(
marketData: CreateMarketDto,
positionData: CreatePositionDto
) {
// Usar transacción de Supabase
const { data, error } = await supabase.rpc('create_market_with_position', {
market_data: marketData,
position_data: positionData
})
if (error) throw new Error('Transaction failed')
return data
}
// Función SQL en Supabase
CREATE OR REPLACE FUNCTION create_market_with_position(
market_data jsonb,
position_data jsonb
)
RETURNS jsonb
LANGUAGE plpgsql
AS $$
BEGIN
-- La transacción comienza automáticamente
INSERT INTO markets VALUES (market_data);
INSERT INTO positions VALUES (position_data);
RETURN jsonb_build_object('success', true);
EXCEPTION
WHEN OTHERS THEN
-- El rollback ocurre automáticamente
RETURN jsonb_build_object('success', false, 'error', SQLERRM);
END;
$$;
```
## Estrategias de Caché
### Capa de Caché con Redis
```typescript
class CachedMarketRepository implements MarketRepository {
constructor(
private baseRepo: MarketRepository,
private redis: RedisClient
) {}
async findById(id: string): Promise<Market | null> {
// Verificar caché primero
const cached = await this.redis.get(`market:${id}`)
if (cached) {
return JSON.parse(cached)
}
// Cache miss - obtener de base de datos
const market = await this.baseRepo.findById(id)
if (market) {
// Cachear por 5 minutos
await this.redis.setex(`market:${id}`, 300, JSON.stringify(market))
}
return market
}
async invalidateCache(id: string): Promise<void> {
await this.redis.del(`market:${id}`)
}
}
```
### Patrón Cache-Aside
```typescript
async function getMarketWithCache(id: string): Promise<Market> {
const cacheKey = `market:${id}`
// Intentar caché
const cached = await redis.get(cacheKey)
if (cached) return JSON.parse(cached)
// Cache miss - obtener de DB
const market = await db.markets.findUnique({ where: { id } })
if (!market) throw new Error('Market not found')
// Actualizar caché
await redis.setex(cacheKey, 300, JSON.stringify(market))
return market
}
```
## Patrones de Manejo de Errores
### Manejador de Errores Centralizado
```typescript
class ApiError extends Error {
constructor(
public statusCode: number,
public message: string,
public isOperational = true
) {
super(message)
Object.setPrototypeOf(this, ApiError.prototype)
}
}
export function errorHandler(error: unknown, req: Request): Response {
if (error instanceof ApiError) {
return NextResponse.json({
success: false,
error: error.message
}, { status: error.statusCode })
}
if (error instanceof z.ZodError) {
return NextResponse.json({
success: false,
error: 'Validation failed',
details: error.errors
}, { status: 400 })
}
// Registrar errores inesperados
console.error('Unexpected error:', error)
return NextResponse.json({
success: false,
error: 'Internal server error'
}, { status: 500 })
}
// Uso
export async function GET(request: Request) {
try {
const data = await fetchData()
return NextResponse.json({ success: true, data })
} catch (error) {
return errorHandler(error, request)
}
}
```
### Reintentos con Backoff Exponencial
```typescript
async function fetchWithRetry<T>(
fn: () => Promise<T>,
maxRetries = 3
): Promise<T> {
let lastError: Error
for (let i = 0; i < maxRetries; i++) {
try {
return await fn()
} catchRelated in Web Dev
generating-lwc-components
IncludedLightning Web Components with PICKLES methodology and 165-point scoring. Use this skill when the user creates or edits LWC components, builds wire service patterns, or writes Jest tests for LWC. TRIGGER when: user creates/edits LWC components, touches lwc/**/*.js, .html, .css, .js-meta.xml files, or asks about wire service, SLDS, or Jest LWC tests. DO NOT TRIGGER when: Apex classes (use generating-apex), Aura components, or Visualforce.
tanstack-query
IncludedManage server state in React with TanStack Query v5. Set up queries with useQuery, mutations with useMutation, configure QueryClient caching strategies, implement optimistic updates, and handle infinite scroll with useInfiniteQuery. Use when: setting up data fetching in React projects, migrating from v4 to v5, or fixing object syntax required errors, query callbacks removed issues, cacheTime renamed to gcTime, isPending vs isLoading confusion, keepPreviousData removed problems.
document-processor-api
IncludedProcess documents with Nutrient DWS. Use when the user wants to generate PDFs from HTML or URLs, convert Office/images/PDFs, assemble or split packets, OCR scans, extract text/tables/key-value pairs, redact PII, watermark, sign, fill forms, optimize PDFs, or produce compliance outputs like PDF/A or PDF/UA. Triggers include convert to PDF, merge these PDFs, OCR this scan, extract tables, redact PII, sign this PDF, make this PDF/A, or linearize for web delivery.
nutrient-document-processing
IncludedProcess documents with Nutrient DWS. Use when the user wants to generate PDFs from HTML or URLs, convert Office/images/PDFs, assemble or split packets, OCR scans, extract text/tables/key-value pairs, redact PII, watermark, sign, fill forms, optimize PDFs, or produce compliance outputs like PDF/A or PDF/UA. Triggers include convert to PDF, merge these PDFs, OCR this scan, extract tables, redact PII, sign this PDF, make this PDF/A, or linearize for web delivery.
tanstack-query
IncludedManage server state in React with TanStack Query v5. Covers useMutationState, simplified optimistic updates, throwOnError, network mode (offline/PWA), and infiniteQueryOptions. Use when setting up data fetching, fixing v4→v5 migration errors (object syntax, gcTime, isPending, keepPreviousData), or debugging SSR/hydration issues with streaming server components.
accelint-nextjs-best-practices
IncludedNext.js performance optimization and best practices. Use when writing Next.js code (App Router or Pages Router); implementing Server Components, Server Actions, or API routes; optimizing RSC serialization, data fetching, or server-side rendering; reviewing Next.js code for performance issues; fixing authentication in Server Actions; or implementing Suspense boundaries, parallel data fetching, or request deduplication.