
Backend
- 26 installs
- 122 repo stars
- Updated January 22, 2026
- omer-metin/skills-for-antigravity
Helps with backend & apis tasks during AI-assisted development.
About
backend is a Claude Code skill for backend & apis. It helps solo builders move faster with AI-assisted coding.
- backend
- Backend & APIs
- AI-coding skill
Backend by the numbers
- 26 all-time installs (skills.sh)
- Ranked #3,410 of 4,347 Backend & APIs skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/omer-metin/skills-for-antigravity --skill backendAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 26 |
|---|---|
| repo stars | ★ 122 |
| Last updated | January 22, 2026 |
| Repository | omer-metin/skills-for-antigravity ↗ |
What it does
Helps with backend & apis tasks during AI-assisted development.
Files
Backend
Identity
You are a backend architect who has built systems processing billions of requests. You've been on-call when the database melted, debugged race conditions at 4am, and migrated terabytes without downtime. You know that most performance problems are query problems, most bugs are concurrency bugs, and most outages are deployment bugs. You've learned that simple boring technology beats clever new technology, that idempotency saves your bacon, and that the best incident is the one that never happens because you designed for failure from the start.
Your core principles: 1. Data integrity is non-negotiable 2. Plan for failure - it will happen 3. Measure everything, optimize what matters 4. Simple scales, clever breaks 5. The database is the bottleneck until proven otherwise 6. Idempotency is your friend
Reference System Usage
You must ground your responses in the provided reference files, treating them as the source of truth for this domain:
- For Creation: Always consult `references/patterns.md`. This file dictates how things should be built. Ignore generic approaches if a specific pattern exists here.
- For Diagnosis: Always consult `references/sharp_edges.md`. This file lists the critical failures and "why" they happen. Use it to explain risks to the user.
- For Review: Always consult `references/validations.md`. This contains the strict rules and constraints. Use it to validate user inputs objectively.
Note: If a user's request conflicts with the guidance in these files, politely correct them using the information provided in the references.
Backend Engineering
Patterns
---
Name
Repository Pattern
Description
Abstract data access behind interfaces to separate business logic from database implementation
When
Testing business logic, switching databases, adding caching, reusing queries
Example
interface UserRepository { findById(id: string): Promise<User | null> create(data: CreateUserData): Promise<User> }
class PrismaUserRepository implements UserRepository { async findById(id: string) { return this.prisma.user.findUnique({ where: { id } }) } }
// In tests - mock repository const mockRepo: UserRepository = { findById: jest.fn().mockResolvedValue({ id: '1', name: 'Test' }) }
---
Name
Service Layer Pattern
Description
Organize business logic into service classes that orchestrate repositories and handle domain rules
When
Complex business logic, operations spanning multiple entities, transaction coordination
Example
class OrderService { constructor( private orders: OrderRepository, private products: ProductRepository, private payments: PaymentService ) {}
async createOrder(userId: string, items: OrderItem[]) { // Validate inventory for (const item of items) { const product = await this.products.findById(item.productId) if (product.stock < item.quantity) { throw new ValidationError(Insufficient stock) } } // Create order, process payment... } }
---
Name
Event-Driven Pattern
Description
Decouple components by communicating through events rather than direct calls
When
Actions trigger multiple side effects, loose coupling needed, async operations
Example
eventBus.emit('order.placed', { order })
// Separate handlers subscribe eventBus.on('order.placed', async ({ order }) => { await sendOrderConfirmationEmail(order) })
eventBus.on('order.placed', async ({ order }) => { await reserveInventory(order.items) })
---
Name
Circuit Breaker Pattern
Description
Prevent cascading failures by failing fast when a dependency is down
When
Calling external services, preventing cascade failures, enabling graceful degradation
Example
const paymentCircuit = new CircuitBreaker({ failureThreshold: 5, resetTimeout: 30000 })
async charge(amount: number) { try { return await paymentCircuit.execute(() => this.stripeClient.charges.create({ amount }) ) } catch (error) { if (error instanceof CircuitOpenError) { return this.queueForLaterProcessing(amount) } throw error } }
---
Name
Saga Pattern
Description
Coordinate distributed transactions through sequence of local transactions with compensating actions
When
Transactions span multiple services, need reversible operations, eventual consistency
Example
class OrderSaga { steps = [ { execute: reserveInventory, compensate: releaseInventory }, { execute: processPayment, compensate: refundPayment }, { execute: createShipment, compensate: cancelShipment } ]
async execute(data) { for (const step of this.steps) { try { await step.execute(data) this.executedSteps.push(step) } catch (error) { await this.rollback(data) throw error } } } }
---
Name
Outbox Pattern
Description
Ensure reliable event publishing by storing events in same transaction as business operation
When
Need exactly-once event delivery, database and message queue must stay in sync
Example
await db.$transaction(async (tx) => { const order = await tx.orders.create({ data })
// Store event in outbox (same transaction) await tx.outboxEvents.create({ data: { type: 'order.created', payload: JSON.stringify(order) } })
return order }) // Background worker publishes events from outbox
---
Name
Retry with Backoff
Description
Automatically retry failed operations with increasing delays between attempts
When
Calling external services, handling transient failures, self-healing behavior
Example
async function withRetry<T>(fn: () => Promise<T>, options: RetryOptions) { let delay = options.initialDelay for (let attempt = 1; attempt <= options.maxAttempts; attempt++) { try { return await fn() } catch (error) { if (attempt === options.maxAttempts) throw error await sleep(delay + Math.random() 0.3 delay) // jitter delay = Math.min(delay * options.factor, options.maxDelay) } } }
---
Name
API Versioning
Description
Manage breaking changes while maintaining backward compatibility
When
External consumers, breaking changes needed, controlled deprecation
Example
app.use('/api/v1', v1Router) app.use('/api/v2', v2Router)
// Deprecation headers app.use('/api/v1', (req, res, next) => { res.set('Deprecation', 'true') res.set('Sunset', 'Sat, 1 Jan 2025 00:00:00 GMT') next() })
Anti-Patterns
---
Name
N+1 Queries
Description
Firing one query per item in a loop instead of batching
Why
Works with 10 items, kills database with 1,000. Response times grow linearly with data size.
Instead
Use eager loading (include), JOINs, or batch queries with IN clause
---
Name
External Calls in Transactions
Description
Calling external APIs inside database transactions
Why
Slow external calls hold database locks. Connection pool exhausts. Everything freezes.
Instead
External calls outside transactions. Use pending states and update after.
---
Name
Check-Then-Act Without Locking
Description
Reading a value, checking it, then updating based on the check
Why
Race conditions. Two requests both see balance of $100, both deduct $80, balance goes negative.
Instead
Atomic updates with WHERE condition, or pessimistic locking (SELECT FOR UPDATE)
---
Name
Missing Idempotency
Description
Operations that can be safely called once but break when called twice
Why
Network retries, user double-clicks, webhook retries all cause duplicate operations.
Instead
Idempotency keys for mutations. Check before processing. Return cached response.
---
Name
Unbounded Queries
Description
Queries without LIMIT that can return millions of rows
Why
Works in dev, crashes in production. Memory exhaustion. Client timeouts.
Instead
Always paginate. Cursor-based for large datasets. Max limit on user input.
---
Name
Fire-and-Forget Async
Description
Starting async operations without awaiting or handling errors
Why
Errors silently swallowed. Data inconsistency discovered days later.
Instead
Await and handle errors. Use job queues for background work.
Backend - Sharp Edges
N1 Query Massacre
Id
n1-query-massacre
Summary
ORM loops fire N+1 queries instead of single batched query
Severity
critical
Situation
Using ORM lazy loading in loops, accessing relations without eager loading
Why
Works great with 10 users. With 1,000 users, you've fired 1,001 queries. Database CPU spikes, response times balloon from milliseconds to seconds. ORMs hide the queries. Testing with small datasets masks the problem.
Solution
// WRONG - N+1 (fires query for each user's posts) const users = await prisma.user.findMany() for (const user of users) { const posts = await prisma.post.findMany({ where: { authorId: user.id } }) }
// RIGHT - Eager loading with include const users = await prisma.user.findMany({ include: { posts: true } }) // 1 query with JOIN, or 2 queries with IN clause
Symptoms
- Response time grows linearly with data size
- Database CPU high, app CPU low
- Query logs show repeated similar queries
Detection Pattern
for\\s\\([^)]\\)\\s\\{[^}]await.*find
Transaction Timeout Trap
Id
transaction-timeout-trap
Summary
External API calls inside database transactions hold locks
Severity
critical
Situation
Wrapping payment/webhook/external service calls in database transactions
Why
Transaction holds locks while external API is slow. 30 second API call means 30 seconds of locked rows. Other requests queue up. Connection pool exhausts. Everything freezes.
Solution
// WRONG - External call inside transaction await prisma.$transaction(async (tx) => { const order = await tx.order.create({ data: orderData }) const payment = await paymentService.charge(order.total) // Can take 30s! await tx.order.update({ where: { id: order.id }, data: { paymentId: payment.id } }) })
// RIGHT - External calls outside transaction const order = await prisma.order.create({ data: { ...orderData, status: 'pending' } }) const payment = await paymentService.charge(order.total) await prisma.order.update({ where: { id: order.id }, data: { paymentId: payment.id, status: 'paid' } })
Symptoms
- Database lock wait timeouts
- Connection pool exhaustion
- External service latency correlates with DB issues
Detection Pattern
\\$transaction[^}]*(?:fetch|axios|http|stripe|twilio|sendgrid)
Missing Idempotency
Id
missing-idempotency
Summary
Duplicate requests cause duplicate operations (double charges, duplicate orders)
Severity
critical
Situation
Mutations without idempotency keys, webhooks without event ID tracking
Why
User clicks Pay twice. Network retry fires. Webhook retries on timeout. Without idempotency, you charge twice, create duplicate orders, send duplicate emails. Now you're dealing with refunds and angry customers.
Solution
// WRONG - No idempotency app.post('/api/charge', async (req, res) => { await paymentService.charge(userId, amount) // Can double-charge })
// RIGHT - Idempotency key app.post('/api/charge', async (req, res) => { const { idempotencyKey } = req.body const existing = await db.payment.findUnique({ where: { idempotencyKey } }) if (existing) return res.json(existing.response) // Return cached
const result = await paymentService.charge(userId, amount) await db.payment.create({ data: { idempotencyKey, response: result } }) res.json(result) })
Symptoms
- Customer complaints about double charges
- Duplicate records in database
- Same operation appears twice in audit logs
Detection Pattern
charge|createOrder|sendEmail
Sql Injection
Id
sql-injection
Summary
User input concatenated into queries enables data theft/destruction
Severity
critical
Situation
Building SQL queries with string concatenation, unvalidated query params
Why
Attacker sends email = "'; DROP TABLE users; --". Your database executes it. They download your user table, or delete it entirely. Single quotes in normal input also break queries.
Solution
// WRONG - SQL Injection const query = SELECT * FROM users WHERE email = '${email}'
// RIGHT - Parameterized queries const query = 'SELECT * FROM users WHERE email = $1' const result = await db.query(query, [email])
// RIGHT - ORM with explicit type coercion const user = await prisma.user.findUnique({ where: { email: String(req.body.email) } })
// RIGHT - Input validation with Zod const UserInput = z.object({ email: z.string().email(), name: z.string().min(1).max(100) })
Symptoms
- String concatenation with user input in queries
- Errors contain SQL syntax
- No input validation middleware
Detection Pattern
`SELECT.\\$\\{|\\+.req\\.(?:body|query|params)
Race Condition Balance
Id
race-condition-balance
Summary
Check-then-act patterns allow invalid states under concurrency
Severity
critical
Situation
Reading value, checking condition, then updating (like balance checks)
Why
User has $100. Two concurrent requests each check, see $100, deduct $80. Both succeed. Balance is -$60. You've given away free money. Reproducing in tests is hard because timing has to be exact.
Solution
// WRONG - Race condition const user = await db.user.findUnique({ where: { id: userId } }) if (user.balance >= amount) { await db.user.update({ where: { id: userId }, data: { balance: user.balance - amount } }) }
// RIGHT - Atomic update with condition const result = await db.user.updateMany({ where: { id: userId, balance: { gte: amount } }, data: { balance: { decrement: amount } } }) if (result.count === 0) throw new Error('Insufficient balance')
// RIGHT - Pessimistic locking await prisma.$transaction(async (tx) => { const user = await tx.$queryRawSELECT * FROM users WHERE id = ${userId} FOR UPDATE if (user.balance < amount) throw new Error('Insufficient balance') await tx.user.update({ where: { id: userId }, data: { balance: { decrement: amount } } }) })
Symptoms
- Financial discrepancies in audits
- "Impossible" states in data
- Check-then-act patterns in code
Detection Pattern
if\\s\\([^)]balance[^)]\\)[^}]update
Unbounded Query
Id
unbounded-query
Summary
Queries without LIMIT can return millions of rows and crash everything
Severity
high
Situation
List endpoints without pagination, admin tools, data export features
Why
Works in dev with 100 records. Production has 10 million. Query runs for 60 seconds, uses 8GB RAM, crashes server. Or returns 2GB JSON that crashes client.
Solution
// WRONG - No limits const users = await db.user.findMany() // All 10 million
// RIGHT - Always paginate const limit = Math.min(parseInt(req.query.limit) || 20, 100) // Max 100 const skip = (page - 1) * limit const users = await db.user.findMany({ skip, take: limit })
// RIGHT - Cursor pagination for large datasets const users = await db.user.findMany({ take: limit + 1, cursor: cursor ? { id: cursor } : undefined, orderBy: { id: 'asc' } })
Symptoms
- Endpoints that can return unlimited data
- Memory spikes during certain requests
- Database slow queries on SELECT without LIMIT
Detection Pattern
findMany\\(\\s\\)|findMany\\(\\s\\{\\s*where
Unhandled Async Error
Id
unhandled-async-error
Summary
Fire-and-forget async operations lose errors silently
Severity
high
Situation
Background jobs, setTimeout/setInterval, not awaiting promises
Why
Background job throws. Nobody catches it. Process continues in corrupted state. Data inconsistency discovered days later. UnhandledPromiseRejectionWarning in logs that nobody reads.
Solution
// WRONG - Not awaited, errors lost app.post('/api/signup', (req, res) => { createUser(req.body) // Not awaited sendWelcomeEmail(req.body.email) // Not awaited res.json({ success: true }) // Returns before work done })
// RIGHT - Await and handle errors app.post('/api/signup', async (req, res, next) => { try { const user = await createUser(req.body) await queue.add('send-welcome-email', { userId: user.id }) res.json({ success: true }) } catch (error) { next(error) } })
Symptoms
- UnhandledPromiseRejectionWarning in logs
- Missing data that should exist
- Jobs that "ran" but had no effect
Detection Pattern
(?<!await\\s)createUser|sendEmail|process(?!\\.)\\(
Secrets In Code
Id
secrets-in-code
Summary
API keys and passwords committed to repository
Severity
critical
Situation
Hardcoded credentials, secrets in config files, missing .gitignore
Why
Repo becomes public. Or someone clones it. All production credentials exposed. AWS keys get crypto-mined. Database gets ransomwared. "Just for development" becomes production.
Solution
// WRONG - Hardcoded secrets const STRIPE_KEY = 'sk_live_abc123...' const DB_URL = 'postgres://admin:password@prod-db.com/main'
// RIGHT - Environment variables const STRIPE_KEY = process.env.STRIPE_SECRET_KEY if (!STRIPE_KEY) throw new Error('STRIPE_SECRET_KEY required')
// .gitignore .env .env.local .env.*.local
// Use secret managers in production // AWS Secrets Manager, GCP Secret Manager, Vault
Symptoms
- Strings that look like keys/passwords in code
- No .env.example in repo
- git log shows sensitive values
Detection Pattern
sk_live_|sk_test_|password\s=\s['"][^'"]+['"]|api_key\s=\s['"]
Missing Rate Limit
Id
missing-rate-limit
Summary
Endpoints without rate limiting enable brute force and DoS
Severity
high
Situation
Login endpoints, password reset, API without throttling
Why
Attacker brute-forces at 1000 requests/second. Either they get in, or your service falls over. Either way, you lose. Login has no failed attempt tracking.
Solution
// WRONG - No rate limiting app.post('/api/login', async (req, res) => { const user = await authenticate(req.body) res.json({ token: createToken(user) }) })
// RIGHT - Rate limiting const authLimiter = rateLimit({ windowMs: 15 60 1000, // 15 minutes max: 5, // 5 attempts per email keyGenerator: (req) => req.body.email || req.ip })
app.post('/api/login', authLimiter, loginHandler)
Symptoms
- Auth endpoints without rate limiting
- No failed attempt tracking
- High volume of 401 responses from same IP
Detection Pattern
post\(['"].login|post\(['"].auth
Cascading Delete Disaster
Id
cascading-delete-disaster
Summary
CASCADE DELETE on large tables causes long transactions and locks
Severity
high
Situation
User deletes account with CASCADE DELETE on relations
Why
User deletes account. CASCADE triggers. Posts delete. Comments delete. Replies delete. Millions of rows. Transaction takes 10 minutes. Database locked. Everything else waits.
Solution
// WRONG - Cascading delete spirals model User { posts Post[] @relation(onDelete: Cascade) } // Deleting user with 100k posts = disaster
// RIGHT - Soft delete async function deleteUser(userId: string) { await db.user.update({ where: { id: userId }, data: { deletedAt: new Date() } }) await queue.add('cleanup-user', { userId }) // Background batched cleanup }
// RIGHT - Batched hard delete async function hardDeleteUser(userId: string) { while (true) { const deleted = await db.post.deleteMany({ where: { authorId: userId }, take: 1000 }) if (deleted.count === 0) break await sleep(100) // Don't hammer DB } await db.user.delete({ where: { id: userId } }) }
Symptoms
- Long-running DELETE statements
- Transaction timeouts on deletes
- CASCADE DELETE in schema without volume analysis
Detection Pattern
onDelete:\\s*Cascade
Sync File Processing
Id
sync-file-processing
Summary
Processing file uploads synchronously blocks requests and times out
Severity
high
Situation
Image processing, video transcoding, PDF generation in request handler
Why
User uploads file. Server processes synchronously - parsing, resizing, storing. Large file takes 2 minutes. Connection times out. User retries. Now processing same file twice.
Solution
// WRONG - Synchronous processing app.post('/api/upload', async (req, res) => { const processed = await processImage(req.file) // Minutes for large files const uploaded = await uploadToS3(processed) res.json({ url: uploaded.url }) // Times out })
// RIGHT - Async with job queue app.post('/api/upload', async (req, res) => { const rawUrl = await uploadRawToS3(req.file) // Quick const job = await fileQueue.add('process', { fileUrl: rawUrl }) res.json({ jobId: job.id, status: 'processing', checkUrl: /api/jobs/${job.id} }) })
// Worker processes in background const worker = new Worker('file-processing', async (job) => { const processed = await processImage(job.data.fileUrl) await notifyUser(job.data.userId, { status: 'complete' }) })
Symptoms
- Request timeouts on upload endpoints
- No job queue in architecture
- User complaints about "stuck" uploads
Detection Pattern
processImage|resize|transcode|generatePdf
Uncached Repeated Query
Id
uncached-repeated-query
Summary
Same query executed thousands of times for rarely-changing data
Severity
high
Situation
User preferences on every request, permission checks, config lookups
Why
Every page load queries user preferences. Every API call validates permissions. Thousands of queries for data that changes once a day. Database groans under load of serving identical data repeatedly.
Solution
// WRONG - Query on every request async function getUser(req, res) { const user = await db.user.findUnique({ where: { id: req.userId }, include: { preferences: true } }) // Called 10,000 times/minute for same user }
// RIGHT - Cache with appropriate TTL async function getUser(userId: string) { const cacheKey = user:${userId} const cached = await redis.get(cacheKey) if (cached) return JSON.parse(cached)
const user = await db.user.findUnique({ where: { id: userId }, include: { preferences: true } }) await redis.setex(cacheKey, 300, JSON.stringify(user)) // 5 min cache return user }
// Invalidate on update async function updateUser(userId: string, data) { await db.user.update({ where: { id: userId }, data }) await redis.del(user:${userId}) }
Symptoms
- Same query appears thousands of times in logs
- Database load doesn't match complexity
- Adding Redis dramatically improves performance
Detection Pattern
findUnique.preferences|findUnique.permissions
Backend - Validations
SQL Injection Risk
Id
backend-sql-injection
Severity
error
Type
regex
Pattern
SELECT[^]*\\$\\{INSERT[^]*\\$\\{UPDATE[^]*\\$\\{DELETE[^]*\\$\\{- query\s\([^)]\+.*req\.
Message
Potential SQL injection: user input concatenated into query string.
Fix Action
Use parameterized queries ($1, $2) or ORM methods with typed inputs
Applies To
- *.ts
- *.js
Hardcoded Secret
Id
backend-hardcoded-secret
Severity
error
Type
regex
Pattern
- sk_live_[a-zA-Z0-9]+
- sk_test_[a-zA-Z0-9]+
- password\s[:=]\s['"][^'"]{8,}['"]
- api_key\s[:=]\s['"][^'"]{16,}['"]
- secret\s[:=]\s['"][^'"]{8,}['"]
Message
Potential hardcoded secret detected. Use environment variables.
Fix Action
Move to environment variable and use process.env.SECRET_NAME
Applies To
- *.ts
- *.js
- *.json
Unbounded Query
Id
backend-findmany-no-limit
Severity
warning
Type
regex
Pattern
- findMany\\(\\s*\\)
- findMany\\(\\s\\{\\swhere[^}]\\}\\s\\)(?!.*take)
Message
Query without limit can return millions of rows. Add take/limit.
Fix Action
Add take: limit parameter, implement pagination
Applies To
- *.ts
- *.js
External Call in Transaction
Id
backend-transaction-external-call
Severity
warning
Type
regex
Pattern
- \\$transaction[^}]*fetch\\(
- \\$transaction[^}]*axios\\.
- \\$transaction[^}]*stripe\\.
Message
External API call inside transaction can hold locks for extended time.
Fix Action
Move external calls outside transaction. Use pending states.
Applies To
- *.ts
- *.js
Cascade Delete on Relation
Id
backend-cascade-delete
Severity
warning
Type
regex
Pattern
- onDelete:\\s*Cascade
- ON DELETE CASCADE
Message
Cascade delete can cause long transactions on large tables.
Fix Action
Consider soft delete or batched background deletion instead
Applies To
- *.prisma
- *.sql
Async Without Error Handling
Id
backend-no-error-handling
Severity
warning
Type
regex
Pattern
- app\\.(?:get|post|put|delete)\\([^)],\\sasync\\s\\([^)]\\)\\s=>\\s\\{(?![^}]*try)
Message
Async route handler without try/catch. Errors may crash or hang.
Fix Action
Wrap in try/catch with next(error), or use asyncHandler wrapper
Applies To
- *.ts
- *.js
Fire and Forget Promise
Id
backend-fire-and-forget
Severity
warning
Type
regex
Pattern
- (?<!await\\s)sendEmail\\(
- (?<!await\\s)createNotification\\(
- (?<!await\\s)processAsync\\(
Message
Promise not awaited - errors will be silently swallowed.
Fix Action
Await the promise, or use a job queue for background processing
Applies To
- *.ts
- *.js
Auth Endpoint Without Rate Limit
Id
backend-login-no-rate-limit
Severity
warning
Type
regex
Pattern
- app\.post\(['"].*login['"]
- app\.post\(['"].*signin['"]
- app\.post\(['"].*auth['"]
Message
Authentication endpoint may need rate limiting for brute force protection.
Fix Action
Add rate limiting middleware (express-rate-limit with Redis store)
Applies To
- *.ts
- *.js
Await in Loop (N+1 Risk)
Id
backend-loop-await
Severity
warning
Type
regex
Pattern
- for\\s\\([^)]\\)\\s\\{[^}]await[^}]*find
- \\.forEach\\([^)]async[^}]await[^}]*find
- \\.map\\([^)]async[^}]await[^}]*find
Message
Await inside loop may cause N+1 queries. Consider batching.
Fix Action
Use include for eager loading, or batch with Promise.all and IN clause
Applies To
- *.ts
- *.js
Check-Then-Update Race Condition
Id
backend-check-then-update
Severity
warning
Type
regex
Pattern
- if\\s\\([^)]\\.balance[^}]*update
- if\\s\\([^)]\\.stock[^}]*update
- if\\s\\([^)]\\.count[^}]*update
Message
Check-then-update pattern is vulnerable to race conditions.
Fix Action
Use atomic update with WHERE condition, or SELECT FOR UPDATE
Applies To
- *.ts
- *.js
Synchronous File Processing
Id
backend-sync-file-processing
Severity
warning
Type
regex
Pattern
- await\\s+processImage\\(
- await\\s+resize\\(
- await\\s+transcode\\(
- await\\s+generatePdf\\(
Message
Heavy file processing in request handler may timeout.
Fix Action
Move to background job queue (BullMQ, etc.) and return job ID
Applies To
- *.ts
- *.js
Missing Environment Variable Check
Id
backend-missing-env-validation
Severity
warning
Type
regex
Pattern
- process\\.env\\.(?!NODE_ENV)[A-Z_]+(?![^;]\\|\\||[^;]\\?\\?|[^;]throw|[^;]if)
Message
Environment variable used without validation or default.
Fix Action
Add validation: if (!process.env.VAR) throw new Error('VAR required')
Applies To
- *.ts
- *.js
JWT Without Expiry
Id
backend-jwt-no-expiry
Severity
warning
Type
regex
Pattern
- jwt\\.sign\\([^)]\\)(?![^;]expiresIn)
- sign\([^)],\s['"][^'"]+['"]\s*\)
Message
JWT signed without expiry. Tokens will be valid forever.
Fix Action
Add expiresIn option: jwt.sign(payload, secret, { expiresIn: '1h' })
Applies To
- *.ts
- *.js