You're Invited:Meet the Socket Team at BlackHat and DEF CON in Las Vegas, Aug 4-6.RSVP
Socket
Book a DemoInstallSign in
Socket

rsdi

Package Overview
Dependencies
Maintainers
1
Versions
44
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

rsdi

TypeScript dependency injection container. Strong types without decorators.

3.0.4
latest
npmnpm
Version published
Weekly downloads
2.9K
-9.3%
Maintainers
1
Weekly downloads
 
Created
Source

RSDI - Simple & Strong-Type Dependency Injection Container

Manage your dependencies with ease and safety. RSDI is a minimal, powerful DI container with full TypeScript support — no decorators or metadata required.

  • Motivation
  • Features
  • Best Use Cases
  • Architecture
  • How to use
  • Strict types
  • Wiki

Motivation

Most DI libraries rely on reflect-metadata and decorators to auto-wire dependencies. But this tightly couples your business logic to a framework — and adds complexity:

@injectable()
class Foo {
  constructor(@inject("Database") private database?: Database) {}
}
// Notice how in order to allow the use of the empty constructor new Foo(), 
// we need to make the parameters optional, e.g. database?: Database.

Why should your core logic even know it’s injectable?

RSDI avoids this by using explicit factory functions — making your code clean, framework-agnostic, and easier to test.

RSDI avoids this by letting you define dependencies in a simple and clear way — keeping your code clean, decoupled from frameworks, and easy to test.

Read more

Features

  • No decorators
  • Strong TypeScript support
  • Simple API
  • No runtime dependencies
  • Easy to mock and test

Best Use Cases

Use RSDI when your app grows in complexity:

  • You break big modules into smaller ones
  • You have deep dependency trees (A → B → C)
  • You want to pass dependencies across layers:
    • Controllers
    • Domain managers
    • Repositories
    • Infrastructure services

Architecture

RSDI works best when you organize your app as a dependency tree.

A typical backend app might have:

  • Controllers (REST or GraphQL)
  • Domain managers (use-cases, handlers)
  • Repositories (DB access)
  • Infrastructure (DB pools, loggers)

architecture

Set up your DI container at the app entry point — from there, all other parts can pull in what they need.

How to use

Basic Example

const container = new DIContainer()
    .add("a", () => "name1")
    .add("bar", () => new Bar())
    .add("foo", ({ a, bar}) => new Foo(a, bar));

const { foo } = container; // alternatively  container.get("foo");

Real-World Example

// sample web application components

export function UserController(
  userRegistrator: UserRegistrator,
  userRepository: UserRepository,
) {
  return {
    async create(req: Request, res: Response) {
      const user = await userRegistrator.register(req.body);
      res.send(user);
    },
    async list(req: Request) {
      const users = await userRepository.findAll(req.body);
      res.send(users);
    },
  };
}

export class UserRegistrator {
  public constructor(public readonly userRepository: UserRepository) {}

  public async register(userData: SignupData) {
    // validate and send sign up email
    return this.userRepository.saveNewUser(userData);
  }
}

export function MyDbProviderUserRepository(db: DbConnection): UserRepository {
  return {
    async saveNewUser(userAccountData: SignupData): Promise<void> {
      await this.db("insert").insert(userAccountData);
    },
  };
}

export function buildDbConnection(): DbConnection {
  return connectToDb({
    /* db credentials */
  });
}

Now let’s configure the dependency injection container. Dependencies are only created when they’re actually needed. Your configureDI function will declare and connect everything in one place.

import { DIContainer } from "rsdi";

export type AppDIContainer = ReturnType<typeof configureDI>;

export default function configureDI() {
  return new DIContainer()
    .add("dbConnection", buildDbConnection())
    .add("userRepository", ({ dbConnection }) =>
      MyDbProviderUserRepository(dbConnection),
    )
    .add("userRegistrator", ({ userRepository }) => new UserRegistrator(userRepository))
    .add("userController", ({ userRepository, userRegistrator}) =>
      UserController(userRepository, userRegistrator),
    );
}

When a resolver runs for the first time, its result is cached and reused for future calls.

By default, you should always use .add() to register dependencies. If you need to replace an existing one — usually in tests — you can use .update() instead. This avoids accidental overwrites and keeps your setup predictable.

Let's map our web application routes to configured controllers

// configure Express router
export default function configureRouter(
  app: core.Express,
  diContainer: AppDIContainer,
) {
  const { usersController } = diContainer;
  app
    .route("/users")
    .get(usersController.list)
    .post(usersController.create);
}

Add configureDI() in your app’s entry point:

// express.ts
const app = express();

const diContainer = configureDI();
configureRouter(app, diContainer);

app.listen(8000);

🔗 Full example: Express + RSDI

Strict types

RSDI uses TypeScript’s type system to validate dependency trees at compile time, not runtime.

strict type

This gives you autocomplete and safety without decorators or metadata hacks.

Best practices

As your application grows, it’s a good idea to split your DI container setup into multiple files. This helps keep your code organized and easier to maintain.

For example, you might have a main diContainer.ts file that sets up the core container, and then separate files like controllers.ts, validators.ts, or dataAccess.ts that each register a group of related dependencies.

This modular approach makes it easier to manage changes, test in isolation, and understand how dependencies are wired across different parts of your app.


// diContainer.ts

export const configureDI = async () => {
  return (await buildDatabaseDependencies())
    .extend(addDataAccessDependencies)
    .extend(addValidators);
}

// addDataAccessDependencies.ts
export type DIWithPool = Awaited<ReturnType<typeof buildDatabaseDependencies>>;
export const addDataAccessDependencies = async () => {
  const pool = await createDatabasePool();
  const longRunningPool = await createLongRunningDatabasePool();
  return new DIContainer()
    .add("databasePool", () => pool)
    .add("longRunningDatabasePool", () => longRunningPool);
};

//  addValidators.ts
export type DIWithValidators = ReturnType<typeof addValidators>;
export const addValidators = (container: DIWithPool) => {
  return container
    .add('myValidatorA', ({ a, b, c }) => new MyValidatorA(a, b, c))
    .add('myValidatorB', ({ a, b, c }) => new MyValidatorB(a, b, c));
};

Keywords

dependency injection

FAQs

Package last updated on 12 Apr 2025

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts