npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

iocta

v0.0.3

Published

Simple Dependency Injection containers for TypeScript

Readme

TypeScript IoC Container Library

A lightweight, type-safe Inversion of Control (IoC) container for TypeScript that enables modular application architecture with dependency injection, automatic lifecycle management, and explicit dependency graphs.

Idea, goals, what's included

The idea is the same as in NestJS: the app is organized in vertical slices, every slice has an explicit module file, modules depend on each other, dependencies are injected via constructors. All the services are instantiated and dependencies are injected by the framework, not manually.

Goals:

  • no classes
  • no decorators
  • no tokens
  • explicit dependencies (no "Service Locator" pattern)

Not implemented:

  • circular dependencies: modules can import each other, but two services cannot inject each other. This is achievable and could be implemented if you need it and open issue.
  • async: all factory functions, run and stop callbacks are synchronous. stop could support async easily though. It could be supported with additional syntax if needed - open issue.
  • importPick and injectPick are only picking the TS type, but not the runtime object, the full object is injected for now.
  • better syntax for re-exporting (you can see the current syntax in example/src/infrastructure/infra.module.ts).

When it fits:

  • when you prefer a style of factory functions with explicit type declarations (examples below)
  • modular "vertical slice" structure

Installation

npm install iocta

Full example

See the example for a complete example of server application using iocta.

Module

All the components are optional, modules can have:

  • imports: import from other modules;
  • internal: services that are used withing the module, not exposed to others;
  • exports: other modules can import it, and it is included in runModules result;
  • run: a function that is called by runModules;
  • stop: is called when you call stop returned by runModules.
export const authModule = defineModule({
  // A module can import functionality from other modules:
  imports: () => ({
    // `import` imports full services:
    ...userModule.import("userService", "userMiddleware"),
    // `importPick` imports dependencies granularly:
    ...infraModule.importPick({
      // app is the app router - needed for controller
      app: true, // true for importing the full object
      config: ["jwtSecret"], // providing explicit list of things we want to import from it
      db: true,
    }),
  }),
  // `internal` is for things that can only be injected within this module,
  // cannot be imported to other modules.
  internal: () => ({
    authService,
  }),
  // Exporting functionality other modules can use:
  exports: () => ({
    authMiddleware,
    // Re-exporting example:
    // `userService` is imported about and can be inject and exported here.
    userService: ({ userService } = authModule.inject("userService")) =>
      userService,
  }),
  // It is executed once all modules are resolved,
  // It's useful for controllers when they need to add routes to the app router.
  // Can be used for any custom logic that must run when the app starts.
  run: authController,
});

Injectables

Injectables must be factory functions. It's basically a class, but a function. iocta is designed so that all your services have to be factory functions.

Every injectable requires an explicit interface. TypeScript cannot infer it because the function depends on the module recursively. But this is a good practice anyway: you can see what functionality is provided by this service without looking at the implementation.

// Explicit interface:
export interface AuthService {
  register(userData: CreateUserData): Promise<User>;
  login(email: string, password: string): Promise<User>;
  // ...
}

// Factory function:
export const authService = (
  // Injecting dependencies:
  // can only inject from the current module, cannot inject from others.
  // `inject` injects full services.
  deps = authModule.inject("config", "db"),
  // Alternatively, `injectPick` injects dependencies granularly:
  pickedDeps = authModule.injectPick({
    config: ["jwtSecret"],
    db: true, // true injects a full service
  }),
  // Explicit return type is required:
): AuthService => {
  const {
    config: { jwtSecret },
    db,
  } = deps;

  return {
    async register(userData) {
      // ...
    },
    async login(userData) {
      // ...
    },
  };
};

Wiring and running modules

Use runModules to bundle all the modules, instantiate all dependencies.

It calls run functions for all modules having it.

const {
  modules: {
    // Can extract any exported object:
    infraModule: { app },
  },
  stop, // call the stop function to call all modules' stop functions
} = runModules({
  // List all the modules:
  infraModule,
  authModule,
  // ...
});

app.listen(() => {
  /* ... */
});