Adopt it one module at a time
No big-bang migration needed. Your classes stay as they are; wire one module, ship it, then the next. Works in plain JavaScript too.
Bring it into a legacy app, or build a new system without a framework's opinions. Plain classes, typed wiring, no decorators required, not invasive. It is easier than you think
Mature frameworks come with opinions about modules, HTTP, configuration and lifecycle. injecute brings a container and nothing else, so a new module gets exactly the structure it needs and still plugs into the system around it.
When a new project doesn't fit a framework's template, injecute gives you a set of tools and the flexibility to solve the problems you meet as the architecture grows: forks and modules, tags, lifecycle hooks and your own way to register services.
injecute gives an agent what it needs to get the wiring right without guessing:
llms.txt, every guide as Markdown and TSDoc with an example on every export live in node_modules/injecute, so they never drift from the code. An Agent Skill has the rules for writing, reviewing and debugging it.See Using injecute with AI agents to set it up.
| Your situation | Choice |
|---|---|
| A large app that can't stop for a rewrite | injecute: adopt it module by module |
| A new service or module that will grow | injecute: explicit, typed wiring from day one |
| A modular monolith | injecute: typed modules with private services |
| An SDK that other teams install | injecute inside, consumers get a plain object (how) |
| Much of the code is written by coding agents | injecute: docs and a skill in the package, errors agents can fix |
| You want one framework to decide HTTP, validation and modules | A full framework such as NestJS |
| A small script or a typical single-page app | No container: imports are enough (why) |
| You need just a CRUD API | A framework with CRUD built in |
import {
DIContainer,
startable,
startLifecycle,
type ServiceRegistry,
} from 'injecute';
// a module asks only for what it needs and registers its part of the app
const addUsers = (c: ServiceRegistry<{ db: Database }>) =>
c.addSingleton('users', UserRepository, ['db']);
const app = new DIContainer()
.addInstance('dbUrl', 'postgres://localhost/app')
.addSingleton('db', Database, ['dbUrl'])
.extend(addUsers)
.extend(
startable('server', HttpServer, ['users'], {
start: (server) => server.listen(3000),
stop: (server) => server.close(),
}),
);
app.get('users'); // UserRepository, typed
// start the server; on shutdown, close it, then dispose the container
const running = await startLifecycle(app);
process.once('SIGTERM', () => running.stop());
// in a test: replace the database for the whole graph, without touching `app`
await using test = app
.fork({ isolated: true })
.addInstance('db', fakeDb, { replace: true });No decorators are needed, but if you want them, a recipe builds them on top of the container.
Starts and stops servers and consumers in stages, and closes every connection with one call. Works with Express, Fastify, Hono, Next.js and GraphQL, on Node.js, Deno, Bun and in browsers, with no runtime dependencies.