Skip to content

Conclusion

Throughout this guide, we’ve explored the “Worst Backend Ever” — a project intentionally designed to showcase every possible antipattern in backend development. By studying these mistakes, we can better recognize and avoid them in our own code.


AntipatternProblemSolution
God ObjectOne file/class does everythingSplit by responsibility (SRP)
Service LocatorHidden dependenciesUse constructor injection
Circular DependenciesA → B → ADependency inversion, events
Copy-Paste InheritanceDuplicated code everywhereProper inheritance, composition
Singleton AbuseGlobal state, untestableDependency injection
AntipatternProblemSolution
Broken Access ControlAnyone can access anythingAlways check authorization
Cryptographic FailuresWeak hashing, exposed secretsbcrypt/Argon2, environment variables
Injection AttacksUser input executed as codeParameterized queries, input validation
Authentication FailuresWeak passwords, no rate limitingStrong auth, MFA, rate limiting
Security MisconfigurationDebug mode in productionEnvironment-based configuration
AntipatternProblemSolution
Monkey PatchingModified built-in prototypesUtility functions, wrapper classes
Magic NumbersUnexplained numeric literalsNamed constants, enums
Callback HellPyramid of doomasync/await, Promises
God MiddlewareOne middleware does everythingSeparate, composable middleware
AntipatternProblemSolution
Global Mutable StateUnpredictable behaviorImmutable state, DI
Feature Flag ChaosConflicting, never-removed flagsFeature flag service, cleanup policy
AntipatternProblemSolution
Error SwallowingSilent failuresLog and rethrow, proper recovery
Error ExposureStack traces in responsesSeparate internal/external errors
AntipatternProblemSolution
God Table200+ columns, no normalizationProper normalization, foreign keys
SQL InjectionUser input in queriesParameterized queries, ORM

Every module, class, or function should have one reason to change.

// ❌ God Object - does everything
class UserManager {
authenticate() { }
sendEmail() { }
generateReport() { }
backupDatabase() { }
}
// ✅ Separate concerns
class AuthService { authenticate() { } }
class EmailService { sendEmail() { } }
class ReportService { generateReport() { } }
class BackupService { backupDatabase() { } }

Make dependencies explicit and injectable.

// ❌ Hidden dependency
class UserService {
getUser(id) {
return getService('database').query('SELECT * FROM users WHERE id = ?', [id])
}
}
// ✅ Explicit dependency
class UserService {
constructor(database) {
this.database = database
}
getUser(id) {
return this.database.query('SELECT * FROM users WHERE id = ?', [id])
}
}

Never trust user input. Validate at every layer.

// ✅ Multiple layers of protection
app.post('/users',
rateLimitMiddleware, // Layer 1: Rate limiting
authMiddleware, // Layer 2: Authentication
validateInput(userSchema), // Layer 3: Input validation
checkPermission('create'), // Layer 4: Authorization
async (c) => {
// Layer 5: Parameterized queries
await db.insert(users).values(sanitizedData)
}
)

Don’t swallow errors. Log them, handle them, or let them bubble up.

// ❌ Silent failure
try {
await processPayment()
} catch (e) {
// Shh... pretend nothing happened
}
// ✅ Proper error handling
try {
await processPayment()
} catch (error) {
logger.error('Payment failed', { error, orderId })
throw new PaymentError('Payment processing failed', { cause: error })
}

Security should be the default, not an afterthought.

// ✅ Secure defaults
const config = {
debug: process.env.NODE_ENV !== 'production',
cors: {
origin: process.env.ALLOWED_ORIGINS?.split(',') || [],
credentials: true,
},
rateLimit: {
windowMs: 60 * 1000,
max: 100,
},
}

Complexity is the enemy of security and maintainability.

// ❌ Overly complex
if (user.role === 1 || (user.role === 2 && user.department === 3) ||
(user.isAdmin && !user.isRestricted) || user.specialAccess === 0x8000) {
// What does this even check?
}
// ✅ Simple and clear
const canAccess = permissionService.check(user, 'resource:read')
if (canAccess) {
// Clear intent
}

Before merging any code, ask yourself:

  • Is each file/class focused on a single responsibility?
  • Are dependencies explicit (not hidden in service locators)?
  • Are there any circular imports?
  • Is there duplicated code that should be abstracted?
  • Is user input validated and sanitized?
  • Are queries parameterized?
  • Are secrets stored in environment variables?
  • Is authentication/authorization checked on every protected route?
  • Are passwords hashed with bcrypt/Argon2?
  • Are there any magic numbers that should be named constants?
  • Is async code using async/await (not callback hell)?
  • Are built-in prototypes left unmodified?
  • Is middleware focused and composable?
  • Is global mutable state avoided?
  • Are feature flags documented and managed?
  • Are errors logged with context?
  • Are internal errors hidden from users?
  • Is there proper error recovery or propagation?
  • Is the schema properly normalized?
  • Are all queries using parameters?
  • Are sensitive fields encrypted or hashed?

Terminal window
# ESLint with security plugins
npm install eslint eslint-plugin-security eslint-plugin-no-secrets
# Find vulnerabilities in dependencies
npm audit
npx snyk test
Terminal window
# Complexity analysis
npx complexity-report src/
# Dependency visualization
npx madge --circular --image graph.svg src/
# Type checking
npx tsc --noEmit
Terminal window
# SQL injection testing
sqlmap -u "http://localhost:3000/api/users?id=1"
# General vulnerability scanning
npx retire

The “Worst Backend Ever” project demonstrates what happens when antipatterns accumulate:

  • 🐛 Bugs become inevitable — hidden dependencies and global state make behavior unpredictable
  • 🔓 Security becomes impossible — without proper practices, vulnerabilities multiply
  • 🧪 Testing becomes a nightmare — tightly coupled code can’t be tested in isolation
  • 📈 Scaling becomes impractical — god objects and god tables become bottlenecks
  • 👥 Onboarding takes forever — new developers can’t understand the codebase

By following the principles in this guide, you can avoid these pitfalls and build backends that are:

  • Secure — defense in depth, no exposed secrets
  • Maintainable — single responsibility, clear dependencies
  • Testable — dependency injection, isolated modules
  • Scalable — proper architecture, normalized database
  • Understandable — clear naming, no magic numbers


🎉 Happy Coding! 🎉