When I started building StreetJS, I thought the hardest part would be implementing features.
It wasn't.
The hardest part has been deciding what should belong in the framework—and what shouldn't.
At first, adding a feature feels simple.
Need authentication? Add it.
Need WebSockets? Add them.
Need database support? Add it.
Need caching? Add it.
Need telemetry? Add it.
But eventually you realize that every feature creates a new architectural boundary.
And that's where framework development gets interesting.
Every feature creates a question
Suppose StreetJS supports PostgreSQL.
Should PostgreSQL become deeply integrated into the framework?
Or should database access be abstracted so developers can replace it?
What about authentication?
Should StreetJS provide one authentication system?
Or should developers be able to plug in their own strategy?
What about queues?
Should the framework own the queue implementation?
Or should it define an interface and allow different providers?
These decisions matter because APIs are easy to change when nobody uses them.
Once developers build applications around them, changing them becomes expensive.
Strong defaults, not permanent decisions
One principle I'm trying to follow with StreetJS is:
Make the common path simple without making the uncommon path impossible.
Developers should get sensible defaults.
But they should also have escape hatches.
If someone wants to use the framework's built-in functionality, it should be easy.
If another developer needs a different implementation, the architecture should allow it.
That's where plugins and clear interfaces become important.
The framework should disappear
This might sound strange, but I think a good framework should become almost invisible while you're building an application.
You shouldn't constantly think:
"How does StreetJS want me to do this?"
You should be thinking:
"How do I solve my application's problem?"
The framework should provide the infrastructure and then get out of the way.
That's one of the design goals I'm working toward.
This also changes how I approach performance
Performance isn't just about making one function execute faster.
It's about the architecture.
Every abstraction has a cost.
Every middleware layer has a cost.
Every dependency has a cost.
Every serialization step has a cost.
Every unnecessary database query has a cost.
So framework performance requires looking at the entire request lifecycle—not just benchmarking isolated functions.
And this is where open source becomes valuable
When you're building alone, it's easy to convince yourself that an architecture makes sense.
Another developer can look at the same design and immediately ask:
"Why are you doing it this way?"
That question is incredibly valuable.
Sometimes the answer is good.
Sometimes you discover that you made something unnecessarily complicated.
That's why I'm interested in getting more developers involved with StreetJS.
Not just contributors who write code.
Developers who will question the architecture, test the APIs, benchmark the framework, report problems, and suggest better approaches.
Where StreetJS is going
I'm continuing to work on the core framework while also thinking about the ecosystem around it.
The long-term goal isn't simply to have a large list of features.
It's to build a backend platform with:
- Clear architectural boundaries
- Strong TypeScript support
- Good developer experience
- Production-focused infrastructure
- Extensible plugins
- Solid testing
- Useful observability
- Predictable APIs
And hopefully, an ecosystem of developers who help shape where it goes next.
If you build Node.js or TypeScript applications, I'd genuinely like your perspective:
What should a backend framework own—and what should it leave to the developer?
That's one of the questions I'm still exploring with StreetJS.
GitHub: https://github.com/hassanmubiru/StreetJS
Documentation: https://hassanmubiru.github.io/StreetJS/