{"id":37800924,"url":"https://github.com/mgdigital/tsinject","last_synced_at":"2026-01-16T15:26:11.754Z","repository":{"id":46363595,"uuid":"417782582","full_name":"mgdigital/tsinject","owner":"mgdigital","description":"Lightweight dependency injection container for TypeScript","archived":false,"fork":false,"pushed_at":"2021-10-24T10:26:51.000Z","size":1155,"stargazers_count":31,"open_issues_count":0,"forks_count":1,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-09-02T01:54:11.885Z","etag":null,"topics":["dependency-injection","ioc","javascript","nodejs","typescript"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mgdigital.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2021-10-16T09:41:07.000Z","updated_at":"2024-11-28T09:12:01.000Z","dependencies_parsed_at":"2022-09-06T01:21:38.175Z","dependency_job_id":null,"html_url":"https://github.com/mgdigital/tsinject","commit_stats":null,"previous_names":[],"tags_count":7,"template":false,"template_full_name":null,"purl":"pkg:github/mgdigital/tsinject","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mgdigital%2Ftsinject","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mgdigital%2Ftsinject/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mgdigital%2Ftsinject/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mgdigital%2Ftsinject/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mgdigital","download_url":"https://codeload.github.com/mgdigital/tsinject/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mgdigital%2Ftsinject/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28479409,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-16T11:59:17.896Z","status":"ssl_error","status_checked_at":"2026-01-16T11:55:55.838Z","response_time":107,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["dependency-injection","ioc","javascript","nodejs","typescript"],"created_at":"2026-01-16T15:26:11.679Z","updated_at":"2026-01-16T15:26:11.743Z","avatar_url":"https://github.com/mgdigital.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# @mgdigital/tsinject\n\nLightweight and flexible dependency injection container for TypeScript.\n\n[![npm version](https://badge.fury.io/js/@mgdigital%2Ftsinject.svg)](https://badge.fury.io/js/@mgdigital%2Ftsinject) [![codecov](https://codecov.io/gh/mgdigital/tsinject/branch/main/graph/badge.svg)](https://codecov.io/gh/mgdigital/tsinject) [![Language grade: JavaScript](https://img.shields.io/lgtm/grade/javascript/g/mgdigital/tsinject.svg?logo=lgtm\u0026logoWidth=18)](https://lgtm.com/projects/g/mgdigital/tsinject/context:javascript)\n\n## Documentation\n\nInstall with `npm add @mgdigital/tsinject` or `yarn add @mgdigital/tsinject`.\n\nSee [the documentation](https://mgdigital.github.io/tsinject/).\n\n## Motivation\n\nSeveral dependency injection solutions exist for TypeScript. Most use either decorators ([Inversify](https://github.com/inversify/InversifyJS); [TSyringe](https://github.com/microsoft/tsyringe)) or static class properties (Angular). This has several drawbacks:\n\n\u003cspan style=\"color:red\"\u003e\u0026cross;\u003c/span\u003e The types of service that can be defined are restricted to class instances.\n\n\u003cspan style=\"color:red\"\u003e\u0026cross;\u003c/span\u003e The code of the class needs modifying to work with the container (e.g. by adding decorators or static properties).\n\n\u003cspan style=\"color:red\"\u003e\u0026cross;\u003c/span\u003e It will only work with the `experimentalDecorators` compiler option enabled.\n\n**tsinject** adopts an alternative approach with several objectives:\n\n\u003cspan style=\"color:green\"\u003e\u0026check;\u003c/span\u003e Flexibility, composability and reusability of components\n\n\u003cspan style=\"color:green\"\u003e\u0026check;\u003c/span\u003e Sharing global resources while avoiding global side effects\n\n\u003cspan style=\"color:green\"\u003e\u0026check;\u003c/span\u003e Achieving loose coupling in large applications\n\n**tsinject** works by defining named factory functions in a container builder, with unique symbols mapping services available in the container to their type. These factory functions can return anything, allowing configuration objects, class instances, functions or any other type of value to be defined as a container service. Any code can be containerized without need for modifications such as annotations or static properties.\n\nAny application that does something useful needs to cause side effects. These might include:\n\n- Reading or writing some data in a database\n- Checking the current date and time\n- Asking the user for input\n- Logging a message to the console\n\nThese capabilities are implemented by components of the application, with some components depending on others, and with the implementation or configuration of components often depending on values read from the environment. The quickest way to allow components to communicate with each other is often via globally defined singleton instances. Importing these global side effects throughout an application can increase complexity, making code more difficult to debug, test and maintain. Instead, by building components that have their dependencies injected, we can create complex but decoupled applications.\n\n## Usage\n\n### Creating a container module and defining services\n\nTake the example of a logging component, that defines services in a container using the following keys (see [./examples/container/loggingModule/keys.ts](https://github.com/mgdigital/tsinject/blob/main/examples/container/loggingModule/keys.ts)):\n\n```typescript\nexport const loggerConfig = Symbol('loggerConfig') // Provides config values for other logger services\nexport const logFormatter = Symbol('logFormatter') // Formats log data to a log line string\nexport const logWriter = Symbol('logWriter') // Writes log lines, e.g. to the console or to a file\nexport const logger = Symbol('logger') // The logger service that will be used by consumers of this component\n```\n\nWe can make a map of container keys to the type of service they represent (see [./examples/container/loggingModule/services.ts](https://github.com/mgdigital/tsinject/blob/main/examples/container/loggingModule/services.ts)):\n\n```typescript\nimport type { ILogger, LogFormatter, LoggerConfig, LogWriter } from '../../logging/types'\nimport type * as keys from './keys'\n\ntype LoggingServices = {\n  [keys.loggerConfig]: LoggerConfig\n  [keys.logFormatter]: LogFormatter\n  [keys.logWriter]: LogWriter\n  [keys.logger]: ILogger\n}\n\nexport default LoggingServices\n```\n\nWe can then create a [ContainerModule](https://mgdigital.github.io/tsinject/modules.html#ContainerModule) by defining a factory function for each service key (see [./examples/container/loggingModule/module.ts](https://github.com/mgdigital/tsinject/blob/main/examples/container/loggingModule/module.ts)):\n\n```typescript\nimport type { ContainerModule } from '@mgdigital/tsinject'\nimport type LoggingServices from './services'\nimport * as processEnvModule from '../processEnvModule'\nimport * as keys from './keys'\nimport consoleLogWriter from '../../logging/consoleLogWriter'\nimport Logger from '../../logging/Logger'\nimport loggerConfigFromEnv from '../../logging/config/loggerConfigFromEnv'\nimport prettyLogFormatter from '../../logging/prettyLogFormatter'\nimport simpleLogFormatter from '../../logging/simpleLogFormatter'\n\nconst loggingModule: ContainerModule\u003c\n  processEnvModule.services \u0026\n  LoggingServices\n\u003e = {\n  // Specify a unique key for the module\n  key: Symbol('loggingModule'),\n  // Provide a function that builds the module in a ContainerBuilder\n  build: builder =\u003e builder\n    // Use another container module that provides services required by this one\n    .use(processEnvModule.default)\n    // Define a config object based on environment variables\n    .define(\n      keys.loggerConfig,\n      container =\u003e loggerConfigFromEnv(\n        container.get(processEnvModule.keys.processEnv)\n      )\n    )\n    // Provide a different implementation depending on environment variable configuration\n    .define(\n      keys.logFormatter,\n      container =\u003e container.get(keys.loggerConfig).pretty\n        ? prettyLogFormatter\n        : simpleLogFormatter\n    )\n    .define(\n      keys.logWriter,\n      () =\u003e consoleLogWriter\n    )\n    .define(\n      keys.logger,\n      container =\u003e new Logger(\n        container.get(keys.logFormatter),\n        container.get(keys.logWriter),\n        container.get(keys.loggerConfig).level\n      )\n    )\n}\n\nexport default loggingModule\n```\n\nWe can now create a container from this module, get the logger service from the container and log something:\n\n```typescript\nimport { newContainerBuilder } from '@mgdigital/tsinject'\nimport * as loggingModule from './examples/container/loggingModule'\n\nconst container = newContainerBuilder()\n  .use(loggingModule.default)\n  .createContainer()\n\nconst logger = container.get(loggingModule.keys.logger)\n\nlogger.info('Logging something!')\n```\n\n**Note:** We should only call [Container.get](https://mgdigital.github.io/tsinject/interfaces/Container.html#get) from within a factory function or from the [composition root](https://freecontent.manning.com/dependency-injection-in-net-2nd-edition-understanding-the-composition-root/), avoiding the [service locator anti-pattern](https://freecontent.manning.com/the-service-locator-anti-pattern/).\n\n### Decorators\n\nDecorators allow us to modify an already-defined service. Let's create a custom logging module that decorates some of the services in the base module defined above:\n\n```typescript\nimport type { ContainerModule } from '@mgdigital/tsinject'\nimport * as loggingModule from './examples/container/loggingModule'\n\nconst customLoggingModule: ContainerModule\u003c\n  loggingModule.services\n\u003e = {\n  key: Symbol('customLoggingModule'),\n  build: builder =\u003e builder\n    .use(loggingModule.default)\n    // Decorate the logger config so that output is always pretty\n    .decorate(\n      loggingModule.keys.loggerConfig,\n      factory =\u003e container =\u003e ({\n        ...factory(container),\n        pretty: true\n      })\n    )\n    // Decorate the log formatter to append an exclamation mark to all log entries\n    .decorate(\n      loggingModule.keys.logFormatter,\n      factory =\u003e container =\u003e {\n        const baseFormatter = factory(container)\n        return (level, message, data) =\u003e\n          baseFormatter(level, message, data) + '!'\n      }\n    )\n}\n```\n\nWe can also use decorators to achieve features that aren't explicitly implemented in this library, such as service tagging, which we can do by defining a service as an array:\n\n```typescript\nimport type { ContainerModule } from '@mgdigital/tsinject'\n\ntype TaggedServiceType = { foo: string }\n\nconst serviceTag = Symbol('serviceTag')\n\ntype ServiceMap = {\n  [serviceTag]: TaggedServiceType[]\n}\n\nconst myModule: ContainerModule\u003c\n  ServiceMap\n\u003e = {\n  key: Symbol('myModule'),\n  build: builder =\u003e builder\n    .define(\n      serviceTag,\n      () =\u003e []\n    )\n}\n\nconst myOtherModule: ContainerModule\u003c\n  ServiceMap\n\u003e = {\n  key: Symbol('myOtherModule'),\n  build: builder =\u003e builder\n    .use(myModule)\n    .decorate(\n      serviceTag,\n      // Add a service to the array of already defined services\n      factory =\u003e container =\u003e [\n        ...factory(container),\n        { foo: 'bar' }\n      ]\n    )\n}\n```\n\nAnd that's it - unlike some other DI containers that claim to be lightweight, tsinject really is tiny and has a simple API, allowing large and complex but loosely coupled applications to be built from small, simple and easily testable components.\n\nSee the [examples](https://github.com/mgdigital/tsinject/tree/main/examples) folder for a more complete application. It includes a simple tasks service with a REST API that can be started by cloning this repository and running `yarn install`, `yarn build` then `yarn example:start`.\n\n---\n\nCopyright (c) 2021 Mike Gibson, https://github.com/mgdigital\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmgdigital%2Ftsinject","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmgdigital%2Ftsinject","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmgdigital%2Ftsinject/lists"}