I thought refactoring our backend would mostly be about rewriting bad code.
I was wrong.
The hardest part was making sure nobody noticed the refactor was happening at all.
I’ve been working on restructuring a production Express backend used for a university QR attendance system with 2000+ daily users.
When I started digging through the codebase, I found things like:
- routes with 3000+ lines
- duplicated JWT middleware
- pg + knex + prisma all used together
- business logic directly inside routes
- runtime schema modifications
- multiple PrismaClient instances across route files
One thing that genuinely surprised me:
Every route file creating its own new PrismaClient() meant the app was opening multiple independent connection pools in production.
The code fix itself was tiny.
The production impact wasn’t.
Another thing I learned during this refactor:
“clean architecture” is the easy part compared to:
- preserving backward compatibility
- not breaking the mobile app
- keeping response shapes identical
- introducing tests into fragile flows
- designing rollback paths before deploys
I also started categorizing features by “refactor risk level”.
For example:
I completely refused to touch attendance/session flows before writing integration tests first because those flows affect real student attendance records.
One of the more interesting migrations was replacing long transactional BEGIN / COMMIT / ROLLBACK flows with prisma.$transaction while still preserving the exact same API behavior.
The deeper I got into this project, the more backend engineering stopped feeling like:
“building APIs”
and started feeling more like:
risk management under production constraints.
Top comments (0)