Why StreetJS has so few runtime dependencies
A full backend framework with HTTP, routing, DI, a native PostgreSQL driver, WebSockets, auth, and jobs — shipped with just three runtime dependencies. Here's how, and why it matters.
Most Node backends pull in hundreds of transitive packages before you write a line of business logic. Each one is attack surface, a supply-chain risk, and a potential breaking change. StreetJS takes the opposite position: implement the hard parts directly on Node core, and depend on almost nothing.
The three dependencies
StreetJS (streetjs) ships with exactly three runtime dependencies:
reflect-metadata— decorator metadata for constructor injection.ws— WebSocket framing.zod— runtime input validation and schema parsing.
Everything else — the HTTP server, router, dependency-injection container, the
PostgreSQL wire-protocol driver, security primitives, the job queue, clustering,
and OpenAPI generation — is built on Node’s standard library (node:net,
node:http, node:crypto, node:cluster, node:stream).
“But isn’t that reinventing the wheel?”
For application code, reusing libraries is usually right. For a framework’s core, every dependency you adopt becomes a dependency your users inherit — transitively, forever. A native implementation means:
- Smaller attack surface. Fewer packages means fewer places for a supply-chain compromise to hide. It also makes an SBOM short enough to actually read.
- No version-range roulette. The framework can’t break because a transitive dependency shipped a bad minor.
- Bounded behavior. Implementing the PostgreSQL wire protocol directly means StreetJS controls backpressure, memory limits, and auth (SCRAM-SHA-256) instead of inheriting whatever a third-party driver does.
The native PostgreSQL driver
The clearest example: instead of depending on pg, StreetJS speaks PostgreSQL
wire protocol v3 over node:net, with SCRAM-SHA-256 authentication and
socket-level streaming. You write ordinary, parameterized queries:
1
2
3
4
const { rows } = await pool.query(
'SELECT id, name FROM items WHERE owner_id = $1 ORDER BY created_at DESC',
[ownerId],
);
No pg, no pg-pool, no native bindings to compile. The same philosophy gives
you native MySQL, MongoDB, Redis, Kafka, and AMQP clients in the official
plugins — each dependency-free.
What it buys you
- Cost. A self-hosted StreetJS service replaces several managed services (auth, realtime, a queue) with in-process features, on a single small VPS.
- Trust. Provenance-signed releases plus a tiny dependency tree make security review tractable. See the Security & Trust Center.
- Longevity. Fewer moving parts means fewer forced upgrades.
The tradeoff, honestly
Native implementations mean the StreetJS team maintains protocol code that other frameworks delegate. That’s a real cost — paid by maintainers, not users — and it’s why the work is covered by wire-protocol, fuzz, load, and chaos test suites in CI. The dependency-free invariant is a deliberate, defended design choice, not an accident.
Start with npx @streetjs/cli create my-app — see Getting Started.