Conclusion
What We’ve Learned
Section titled “What We’ve Learned”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.
The Antipattern Categories
Section titled “The Antipattern Categories”🏗️ Architecture Antipatterns
Section titled “🏗️ Architecture Antipatterns”| Antipattern | Problem | Solution |
|---|---|---|
| God Object | One file/class does everything | Split by responsibility (SRP) |
| Service Locator | Hidden dependencies | Use constructor injection |
| Circular Dependencies | A → B → A | Dependency inversion, events |
| Copy-Paste Inheritance | Duplicated code everywhere | Proper inheritance, composition |
| Singleton Abuse | Global state, untestable | Dependency injection |
🔒 Security Antipatterns
Section titled “🔒 Security Antipatterns”| Antipattern | Problem | Solution |
|---|---|---|
| Broken Access Control | Anyone can access anything | Always check authorization |
| Cryptographic Failures | Weak hashing, exposed secrets | bcrypt/Argon2, environment variables |
| Injection Attacks | User input executed as code | Parameterized queries, input validation |
| Authentication Failures | Weak passwords, no rate limiting | Strong auth, MFA, rate limiting |
| Security Misconfiguration | Debug mode in production | Environment-based configuration |
📝 Code Quality Antipatterns
Section titled “📝 Code Quality Antipatterns”| Antipattern | Problem | Solution |
|---|---|---|
| Monkey Patching | Modified built-in prototypes | Utility functions, wrapper classes |
| Magic Numbers | Unexplained numeric literals | Named constants, enums |
| Callback Hell | Pyramid of doom | async/await, Promises |
| God Middleware | One middleware does everything | Separate, composable middleware |
🔄 State Management Antipatterns
Section titled “🔄 State Management Antipatterns”| Antipattern | Problem | Solution |
|---|---|---|
| Global Mutable State | Unpredictable behavior | Immutable state, DI |
| Feature Flag Chaos | Conflicting, never-removed flags | Feature flag service, cleanup policy |
⚠️ Error Handling Antipatterns
Section titled “⚠️ Error Handling Antipatterns”| Antipattern | Problem | Solution |
|---|---|---|
| Error Swallowing | Silent failures | Log and rethrow, proper recovery |
| Error Exposure | Stack traces in responses | Separate internal/external errors |
🗄️ Database Antipatterns
Section titled “🗄️ Database Antipatterns”| Antipattern | Problem | Solution |
|---|---|---|
| God Table | 200+ columns, no normalization | Proper normalization, foreign keys |
| SQL Injection | User input in queries | Parameterized queries, ORM |
Key Principles to Remember
Section titled “Key Principles to Remember”1. Single Responsibility Principle (SRP)
Section titled “1. Single Responsibility Principle (SRP)”Every module, class, or function should have one reason to change.
// ❌ God Object - does everythingclass UserManager { authenticate() { } sendEmail() { } generateReport() { } backupDatabase() { }}
// ✅ Separate concernsclass AuthService { authenticate() { } }class EmailService { sendEmail() { } }class ReportService { generateReport() { } }class BackupService { backupDatabase() { } }2. Dependency Injection
Section titled “2. Dependency Injection”Make dependencies explicit and injectable.
// ❌ Hidden dependencyclass UserService { getUser(id) { return getService('database').query('SELECT * FROM users WHERE id = ?', [id]) }}
// ✅ Explicit dependencyclass UserService { constructor(database) { this.database = database }
getUser(id) { return this.database.query('SELECT * FROM users WHERE id = ?', [id]) }}3. Defense in Depth
Section titled “3. Defense in Depth”Never trust user input. Validate at every layer.
// ✅ Multiple layers of protectionapp.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) })4. Fail Fast, Fail Loud
Section titled “4. Fail Fast, Fail Loud”Don’t swallow errors. Log them, handle them, or let them bubble up.
// ❌ Silent failuretry { await processPayment()} catch (e) { // Shh... pretend nothing happened}
// ✅ Proper error handlingtry { await processPayment()} catch (error) { logger.error('Payment failed', { error, orderId }) throw new PaymentError('Payment processing failed', { cause: error })}5. Secure by Default
Section titled “5. Secure by Default”Security should be the default, not an afterthought.
// ✅ Secure defaultsconst config = { debug: process.env.NODE_ENV !== 'production', cors: { origin: process.env.ALLOWED_ORIGINS?.split(',') || [], credentials: true, }, rateLimit: { windowMs: 60 * 1000, max: 100, },}6. Keep It Simple
Section titled “6. Keep It Simple”Complexity is the enemy of security and maintainability.
// ❌ Overly complexif (user.role === 1 || (user.role === 2 && user.department === 3) || (user.isAdmin && !user.isRestricted) || user.specialAccess === 0x8000) { // What does this even check?}
// ✅ Simple and clearconst canAccess = permissionService.check(user, 'resource:read')if (canAccess) { // Clear intent}The Checklist
Section titled “The Checklist”Before merging any code, ask yourself:
Architecture
Section titled “Architecture”- 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?
Security
Section titled “Security”- 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?
Code Quality
Section titled “Code Quality”- 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?
State Management
Section titled “State Management”- Is global mutable state avoided?
- Are feature flags documented and managed?
Error Handling
Section titled “Error Handling”- Are errors logged with context?
- Are internal errors hidden from users?
- Is there proper error recovery or propagation?
Database
Section titled “Database”- Is the schema properly normalized?
- Are all queries using parameters?
- Are sensitive fields encrypted or hashed?
Tools That Help
Section titled “Tools That Help”Static Analysis
Section titled “Static Analysis”# ESLint with security pluginsnpm install eslint eslint-plugin-security eslint-plugin-no-secrets
# Find vulnerabilities in dependenciesnpm auditnpx snyk testCode Quality
Section titled “Code Quality”# Complexity analysisnpx complexity-report src/
# Dependency visualizationnpx madge --circular --image graph.svg src/
# Type checkingnpx tsc --noEmitSecurity Testing
Section titled “Security Testing”# SQL injection testingsqlmap -u "http://localhost:3000/api/users?id=1"
# General vulnerability scanningnpx retireFinal Thoughts
Section titled “Final Thoughts”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! 🎉