injex
v1.0.15
Published
Simple, Decorated, Pluggable dependency-injection container for Node JS apps
Downloads
548
Maintainers
Readme
Simple, Decorated, Pluggable dependency-injection container for Node JS apps
Injex is a simple dependency-injection library for Node JS, which helps to organize a project codebase elegantly while keeping it easy to scale and maintain.
Table of content
- Core concept
- What is a Dependency Injection?
- Install
- Basic example
- Example project
- Requirements
- Quick start
- Manually add or remove an object
- Plugins & Hooks
- Available plugins
- Container setup config
- Container API
- Decorators
Core concept
Injex creates a dependency tree between your modules. Using TypeScript decorators, you can define and inject modules into other modules as dependencies. A dependency is an object which can be injected into other dependent objects.
What is a Dependency Injection?
In software engineering, dependency injection is a technique whereby one object supplies the dependencies of another object. A "dependency" is an object that can be used, for example, as a service. Instead of a client specifying which service it will use, something tells the client what service to use. The "injection" refers to the passing of a dependency (a service) into the object (a client) that would use it.
From Wikipedia
Install
Install Injex using NPM or Yarn:
npm install --save injex
Or
yarn add injex
Basic example
// index.ts
await Injex.create({
rootDirs: [
path.resolve(__dirname, "./src")
]
})
.bootstrap()
// src/mailService.ts
@define()
@singleton()
export class MailService implements IMailService {
public sendMessage(message: string) {
console.log(`Sending message: ${message}...`);
}
}
// src/mailManager.ts
@define()
@singleton()
export class MailManager {
@inject() private mailService: IMailService;
public send(message: string) {
this.mailService.sendMessage(message);
}
}
// src/bootstrap.ts
@bootstrap()
export class Bootstrap implements IBootstrap {
@inject() private mailManager: MailManager;
public run() {
console.log("Ready.");
this.mailManager.send("Hello world!");
}
}
> node index
Ready.
Sending message: Hello world!...
Check out the Quickstart guid for more details.
Example project
Check out the example project if you want to see Injex in action.
Requirements
A project should use TypeScript with the experimentalDecorators
compiler flag set to true
, for more information about this flag, read the TypeScript docs about decorators.
Each defined module should be exported from its file so Injex can find and register it into the container. You can use any export method (e.g. export ...
, export default ...
, module.export =
).
Manually add or remove objects
Sometimes you want to add objects to the container manually, Use the addObject
container method like so:
const car = {
model: "Ford",
type: "Mustang",
color: "Black"
};
container.addObject(car, "myCar");
expect(container.get("myCar")).toStrictEqual(car);
container.bootstrap();
Now you can inject "myCar" into other modules using the @inject()
decorator.
@define()
@singleton()
export class CarService {
@inject() private myCar: ICar;
@init()
public initialize() {
console.log(myCar.type); // Mustang
}
}
To remove an object, use the removeObject
container method:
container.removeObject("myCar");
expect(container.get("myCar")).toBeUndefined();
Plugins & Hooks
Injex supports plugins by exporting container hooks to manipulate, intercept, and extend the container abilities. Under the hood, Injex uses the tapable module by webpack, So if you had created a webpack plugin or you know how they work, you can easily create your Injex plugins.
Injex-Plugin is just a class (or simple object) with an apply
method to be invoked with the container instance once created. For example:
class MyAwesomeNotificationsPlugin {
apply(container) {
// apply container hooks here, for example:
container.hooks.beforeRegistration.tap("MyAwesomeNotificationsPlugin", () => {
container.logger.debug("Registration phase started...");
});
}
}
Container hooks
This list describes all the container hooks you can bind to. To bind with a hook, use the following syntax:
container.hooks.afterModuleCreation.tap("PluginName", (module: IModule) => {
// do something here...
});
Some hooks callbacks invoked with arguments.
For more information about the tapable module, refer to the tapable docs.
beforeModuleRequire
- Before requiring all files in project root directories. The callback function invoked with the module path.afterModuleRequire
- After requiring all files in project root directories. The callback function invoked with the module path and themodule.exports
object.beforeRegistration
- Before all modules registration.afterRegistration
- After all modules registration.beforeCreateModules
- Before modules created and injected with dependencies.afterModuleCreation
- After each module created and injected with dependencies. The callback function invoked with themodule: IModule
just created.afterCreateModules
- After modules created and injected with dependencies.berforeCreateInstance
- Before a module creation via singleton or factory method. The callback function invoked with the instance class constructor.
Available plugins
- injex-express-plugin
Turn your express application into injectable controllers to handle application routes in an elegant way. - More plugins to come
Container setup config
When creating new Injex container, you can use the following configurations:
const container = await Injex.create({
rootDirs: [
path.resolve(process.cwd(), "./src")
],
logLevel: LogLevel.Error,
logNamespace: "Injex",
globPattern: "/**/*.js",
plugins: []
});
rootDirs: string[];
- Specify list of root directories to be used when resolving modules.
Default:[path.resolve(process.cwd(), "./src")]
(the/src
folder inside the executable root)
logLevel: LogLevel;
- Set Injex's logger level.
Possible values:LogLevel.Error
,LogLevel.Warn
,LogLevel.Info
,LogLevel.Debug
Default:LogLevel.Error
logNamespace: string;
- Set Injex's log namespace. The namespace will be included in each log.
Default:Injex
globPattern: string;
- When resolving modules on
rootDirs
, this glob used to find the project files.
Default:/**/*.js
plugins: IInjexPlugin[];
A list of plugins, see Plugins for more info.
Default:[]
For example:
const container = await Injex.create({ ... plugins: [ new InjexExpressPlugin({ // ...plugin config... }) ] });
All the container options are optional
Container API
bootstrap()
- Bootstraps the container, creates singletons, factory methods and injects dependencies.
Note that this method may throwDuplicateDefinitionError
if there are module duplications orInitializeMuduleError
if there is an error in one of the@init
methods.
get<T>([name])
- Lookup and retrieve a module by its name. Returns
undefined
if the module does not exist.
addObject([object, name])
- Add an object to the container with the given name.
Note that this method throws theDuplicateDefinitionError
if the module is already defined.
removeObject([name])
- Remove an object by its name.
Decorators
@define()
- Defines a class as a module using the camel-cased version of the class name, or with the name argument if it's passed to the decorator (
@define("myModule")
)
@singleton()
- Defines a module as a singleton, the same instance returns on each
@inject()
orget()
.
@init()
- Define an init method for a module. The method invoked in the bootstrap phase and can return a Promise.
@bootstrap()
- A class with this decorator invoke its
run
method at the end of the bootstrap container phase after all modules initialized. You don't need to use @define() or @singleton() decorators when you use @bootstrap() since the bootstrap decorator automatically defines the module as a singleton. For Example:@bootstrap() export class ProjectBootstrapModule implements IBootstrap { @inject() private mailManager: MailManager; public async run(): Promise<void> { await this.someAsyncTask(); this.mailManager.sendMessage("Bootstrap complete."); } }
Note that the run
method can return a Promise for async bootstrapping.
@inject()
- Used to inject a module as a dependency into other modules. You can use the module name or its type. For example:
@define() class Mail { ... } @define() @singleton() export class MailManager { // Inject a factory method using the module type @inject(Mail) craeteMail: (message: string) => Mail; // Inject a factory method using the module name @inject("mail") craeteMail: (message: string) => Mail; }
License
Are you having an issue? A feature idea? Want to contribute?
Feel free to open an issue or create a pull request