Skip to main content
Back to Blog
Software
April 9, 2026

The Result and the Railroad

I moved the 2026 Node Express template from a quick prototype to a production-grade infrastructure. I spent the last few weeks removing fragile code and replacing it with patterns that hold up under stress. I do not have time for code that might work or might crash based on how a third party library feels that day. I needed a fortress.

logo_image

Kartik Jha

Author

The Result and the Railroad

The biggest win was unifying the error handling. Most Node.js apps are a mess of random try catch blocks and bubbling exceptions. I moved to a Result pattern across the User and Authentication layers.

  • Logic as Data: Every service and repository returns a { success: true, data } or { success: false, error } object.
  • Predictable Flow: This creates a railroad where the controller just checks a boolean and either ships the data or hands the error to the global handler.
  • No Explosions: I stopped using throw for expected domain logic like missing users or invalid passwords.

Forcing the backend to treat errors as data makes the entire system more stable. It is the only way to build a reliable system without the logic collapsing under its own weight.


Hardening the AI Stream

The template includes assistant features, so it needs to stream data fast. I had a lot of proxy slop in the router that I had to clean up.

  • Infrastructure vs Logic: I pulled the streaming plumbing out of the router and into a dedicated AI Controller and Service.
  • Resource Management: I extended the Express Request type to include a proper AbortSignal.
  • Kill Signals: Now, if a user closes their browser, the signal propagates to the internal API and kills the generation immediately.

This prevents me from burning compute cycles on data that no one is reading.


Plugging the Security Holes

The most critical part of this refactor was addressing the data leak identified during the codebase analysis. Endpoints like GET /api/users were returning hashed passwords because the repository and controller were not separated by a security layer.

  • The DTO Gatekeeper: I implemented a toSafeUser mapper in the User Service.
  • Security by Default: The service layer now acts as a boundary. It is impossible for a password hash to reach the controller because the return type is strictly a SafeUser.
  • Repository Purity: The UserRepository still handles the raw data for internal checks, but it never leaks to the public API.

Closing the Loop

I am officially in the endgame for this foundation. The railroad is complete, the types are airtight, and the linter is finally quiet. I have reached a professional standard that allows me to stop worrying about the how and start focusing on the what.