Cerise
Intuitive Dependency Injection (DI) library for Node.js, written in JavaScript and weighing less than 10 KB. Ironically, Cerise does not depend on any package.
API documentation -- Examples -- FAQ
Installation
Install with npm
or yarn
$ npm install cerise$ yarn add cerise
Both CommonJS and ES modules builds are included; the latter will be automatically selected if your build system supports it.
Usage
Using Cerise is dead simple. There are two concepts you'll need to understand first: containers and factories.
createContainer
A container (also known as an injector) is a master object that knows how to create services, thanks to factories.
const createContainer constant factory service = ; // Create a container and immediately register a factory// for the `name` service.const container = ; // You can also register a service for an existing container.container; // You can retrieve services using either container as a// function, or its `proxy` property.;;
There are multiple ways to declare a service: using the constant
, factory
and service
helpers.
constant
When using constant
you cannot depend on an other service. You can register any value: a number, string, function, etc.
container;container; ;;
factory
If you need to depend on an other service, use a factory. factory
takes a function that will be passed container.proxy
(which can be destructured to access other services) and returns a service.
container; // Using destructuringcontainer; // Alternatively, call the proxy as a functioncontainer; ;
You'll notice that constant(x)
is equivalent to factory(() => x)
: it's just sugar.
service
Lastly, service
is passed a class and will return an instance on retrieval. Use it if you're more familiar with OOP.
{ this_concat = concat; this_baseUrl = baseUrl; } { return this; } container; ;
Once again, it's just sugar: service(T)
is equivalent to factory(proxy => new T(proxy))
.
Scopes
Oftentimes you'll want to create a scope from a container. Scopes inherit their parent and their registered service, but can also have their own service. For instance, if you're using Express, you might want to have a master container to store your database connexion, and another container for request-specific data.
const express = ;const Database = ;const createContainer constant = ; const app = ;const container = ; // For each request, create a scope and fetch session data.app; // Session data is available on child scope.app; // Parent container services are also available on child scope.app;
Lifetimes
default lifetime
By default (except for constant
) the factory will be called each time you wish to retrieve a value from a factory.
// Each resolution will result in a new Thing instance being created.container; const foo = ;const bar = ; ;
singletons
You may however wish to specify a lifetime to your factory in order to cache its result.
// Now the first instance will be cached and returned each time.container; const foo = ;const bar = ; ;
scoped
Singletons only make sense on the root container; if you wish to cache a service for scopes you will want to use the scoped
lifetime qualifier:
const winston = ; container; // Create a scope on every requestapp; // Register a *scoped* logger (with request id metadata)app; // Logging middlewareapp;
Saving and restoring state
Root containers' state can be saved and restored which can be useful for testing. For instance, in Mocha's beforeEach
and afterEach
hooks:
;
Utils
Middlewares: Cerise provides middlewares for Express and Koa.
See the API documentation.
Controllers: since calling req.scope
gets old really fast, Cerise also provides a controller
helper -- with an async error handler for convenience. Pass it a callback, and it will get called with req.scope.proxy
, req
, res
and next
.
See the API documentation.
FAQ
How to overwrite a parent service in a scope?
You can register a service with the same name:
const parent = ;parent; const child = parentscope;child; ;;
Can a child scope service depend on a parent scope service of the same name?
Yes, but you cannot depend directly on the parent service.
parent; // This will break: `breadcrumb` on the child cannot depend on `breadcrumb`.child; // Workaround #1: access parent scope directlychild; // Workaround #2 (preferred): register the parent as a child servicechild;child;
Examples
Head over to the examples directory for in-depth examples.
Contributing
Constructive feedback is always welcome! Feel free to create issues if you have any question, suggestion or bug reports. A pull request is also always appreciated.
Clone this repository, run npm install
or yarn
to install the development dependencies, launch npm test -- -w
or yarn test -w
and start hacking!
Before you submit your pull request, please make sur you've run Prettier (npm run lint
or yarn lint
) and that your test coverage is at 100% (npm run coverage
or yarn coverage
).