
Security News
Attackers Are Hunting High-Impact Node.js Maintainers in a Coordinated Social Engineering Campaign
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.
@fastify-decorators/simple-di
Advanced tools
Dependency injection (DI) is widely used mechanism to autowire controller/service dependency. In fastify-decorators DI only available for controllers.
There's few simple steps to enable this library:
@fastify-decorators/simple-di"experimentalDecorators" as "emitDecoratorMetadata" in tsconfig.jsonNote: auto-generated type metadata may have issues with circular or forward references for types.
Service decorator used to make class injectable
my-service.ts:
import { Service } from '@fastify-decorators/simple-di';
@Service()
export class MyService {
calculate() {
doSomething();
}
}
It's possible that some services may require async initialization, for example to setup database connection.
For such reasons library provides the special decorator called @Initializer.
Usage is quite simple, just annotate your async method with it:
database.service.ts:
import { Initializer, Service } from '@fastify-decorators/simple-di';
import { join } from 'node:path';
import { DataSource } from 'typeorm';
import { Message } from '../entity/message';
@Service()
export class ConnectionService {
dataSource = new DataSource({
type: 'sqljs',
autoSave: true,
location: join(process.cwd(), 'db', 'database.db'),
entities: [Message],
logging: ['query', 'schema'],
synchronize: true,
});
@Initializer()
async init(): Promise<void> {
await this.dataSource.init();
}
}
Services may depend on other async services for their init, for such reasons @Initializer accepts array of such services:
import { Initializer, Service } from '@fastify-decorators/simple-di';
import { Message } from '../entity/message';
import { ConnectionService } from '../services/connection.service';
import type { Repository } from 'typeorm';
@Service()
export class MessageFacade {
private repository!: Repository<Message>;
constructor(private connectionService: ConnectionService) {}
@Initializer([ConnectionService])
async init(): Promise<void> {
// because we added DataSourceProvider as a dependency, we are sure it was properly initialized if it reaches
// this point
this.repository = this.connectionService.dataSource.getRepository(Message);
}
async getMessages(): Promise<Message[]> {
return this.repository.find();
}
}
If you need to have stuff executed before service destroyed (e.g. close database connection) you can use @Destructor decorator:
import { Initializer, Destructor, Service } from '@fastify-decorators/simple-di';
import { Message } from '../entity/message';
import { DataSource } from 'typeorm';
@Service()
export class ConnectionService {
dataSource = new DataSource();
@Initializer()
async init(): Promise<void> {
await this.dataSource.initialize();
}
@Destructor()
async destroy(): Promise<void> {
await this.dataSource.destroy();
}
}
The easiest way to inject dependencies to controllers is using constructors:
sample.controller.ts:
import { Controller, GET } from 'fastify-decorators';
import { MyService } from './my-service';
@Controller()
export class SampleController {
constructor(private service: MyService) {}
@GET()
async index() {
return this.service.doSomething();
}
}
Another option to inject dependencies is @Inject decorator:
sample.controller.ts:
import { Controller, GET } from 'fastify-decorators';
import { Inject } from '@fastify-decorators/simple-di';
import { MyService } from './my-service';
@Controller()
export class SampleController {
@Inject(MyService)
private service!: MyService;
@GET()
async index() {
return this.service.doSomething();
}
}
When you use @Inject you need to specify token, so what is token?
Token is kind of identifier of instance to inject.
By default, when you use @Service decorator it uses class object as token, and it can be changed by specifying token explicitly:
my-service.ts:
import { Service } from '@fastify-decorators/simple-di';
@Service('MyServiceToken')
class MyService {}
this way MyService injection token will be MyServiceToken string and this token can be used in both methods:
import { getInstanceByToken } from '@fastify-decorators/simple-di';
import { MyService } from './my-service.ts';
const service = getInstanceByToken<MyService>('MyServiceToken');
| Token | Provides | Description |
|---|---|---|
FastifyInstanceToken | FastifyInstance | Token used to provide FastifyInstance |
It's not possible to use getInstanceByToken for getting FastifyInstance in static fields or decorators options:
import { Controller, FastifyInstanceToken, getInstanceByToken } from 'fastify-decorators';
@Controller()
class InstanceController {
// Will throw an error when bootstrap via controllers list
// This happens because "FastifyInstance" not available before "bootstrap" call but required when controller imported
static instance = getInstanceByToken(FastifyInstanceToken);
}
Library as well provides option to set token at fastify initialization in order to have top-down DI initialization:
blog-service.ts:
export abstract class BlogService {
abstract getBlogPosts(): Promise<Array<BlogPost>>;
}
sqlite-blog-service.ts:
import { BlogService } from './blog-service.js';
import { BlogPost } from '../models/blog-post.js';
@Service()
export class SqliteBlogService extends BlogService {
async getBlogPosts(): Promise<Array<BlogPost>> {
/* ... */
}
}
sqlite-blog-service.ts:
import { BlogService } from './blog-service.js';
import { BlogPost } from '../models/blog-post.js';
export class MySQLBlogService extends BlogService {
async getBlogPosts(): Promise<Array<BlogPost>> {
/* ... */
}
}
blog-controller.ts:
import { BlogService } from '../services/blog-service.js';
@Controller({
route: '/api/blogposts',
})
export class BlogController {
constructor(private blogService: BlogService) {}
@GET()
public async getBlogPosts(req, res): Promise<Array<BlogPosts>> {
return this.blogService.getBlogPosts();
}
}
and finally set BlogService token in index.ts:
if (environment === 'development') {
injectables.injectService(BlogService, SqliteBlogService);
} else if (environment === 'production') {
injectables.injectSingleton(BlogService, new MySQLBlogService());
}
fastify.register(bootstrap, {
/* ... */
});
configureControllerTestThe configureControllerTest(options) function registers a Controller and allow you to mock out the Services for testing functionality.
You can write tests validating behaviors corresponding to the specific result of Controller interacting with mocked services.
Note: if mock was not provided for one or more dependencies than originals will be used.
Usage:
import { FastifyInstance } from 'fastify';
import { configureControllerTest } from '@fastify-decorators/simple-di/testing';
import { AuthController } from '../src/auth.controller';
import { AuthService } from '../src/auth.service';
describe('Controller: AuthController', () => {
let instance: FastifyInstance;
const authService = { authorize: jest.fn() };
beforeEach(async () => {
instance = await configureControllerTest({
controller: AuthController,
mocks: [
{
provide: AuthService,
useValue: authService,
},
],
});
});
afterEach(() => jest.restoreAllMocks());
it(`should reply with 'ok' if authorization success`, async () => {
authService.authorize.and.returnValue(Promise.resolve(true));
const result = await instance.inject({
url: '/authorize',
method: 'POST',
payload: { login: 'test', password: 'test' },
});
expect(result.json()).toEqual({ message: 'ok' });
});
});
The configureControllerTest decorate Fastify instance with controller property which may be used to access controller instance.
Note: controller will be undefined in case "per request" type is used.
Example:
import { FastifyInstance } from 'fastify';
import { configureControllerTest, FastifyInstanceWithController } from '@fastify-decorators/simple-di/testing';
import { AuthController } from '../src/auth.controller';
describe('Controller: AuthController', () => {
let instance: FastifyInstanceWithController<AuthController>;
beforeEach(async () => {
instance = await configureControllerTest({
controller: AuthController,
});
});
afterEach(() => jest.restoreAllMocks());
it(`should reply with 'ok' if authorization success`, async () => {
const controllerInstance = instance.controller;
jest.spyOn(controllerInstance, 'authorize').mockReturnValue(Promise.resolve({ message: 'ok' }));
const result = await instance.inject({
url: '/authorize',
method: 'POST',
payload: { login: 'test', password: 'test' },
});
expect(result.json()).toEqual({ message: 'ok' });
});
});
configureServiceTestThe configureControllerTest(options) is pretty close to configureControllerTest the difference is that this method returns service with mocked dependencies.
Note: if mock was not provided for one or more dependencies then originals will be used.
For those services which has no method with @Initializer decorator, then configureServiceTest will return an instance of it.
Usage:
import { configureServiceTest } from '@fastify-decorators/simple-di/testing';
import { RolesService } from '../src/roles.service';
import { AuthService } from '../src/auth.service';
describe('Service: AuthService', () => {
let service: AuthService;
const rolesService = { isTechnical: jest.fn(), isAdmin: jest.fn() };
beforeEach(() => {
service = configureServiceTest({
service: AuthService,
mocks: [
{
provide: RolesService,
useValue: rolesService,
},
],
});
});
afterEach(() => jest.restoreAllMocks());
it(`should reply with 'ok' if authorization success`, async () => {
rolesService.isTechnical.and.returnValue(true);
rolesService.isAdmin.and.returnValue(false);
const bearer = 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlcyI6W119.0Dd6yUeJ4UbCr8WyXOiK3BhqVVwJFk5c53ipJBWenmc';
const result = service.hasSufficientRole(bearer);
expect(result).toBe(true);
});
});
If service has method with @Initializer decorator, then configureServiceTest will return intersection of an instance and Promise.
You can work with service like it has no @Initializer unless you await it.
import { configureServiceTest } from '@fastify-decorators/simple-di/testing';
import { RolesService } from '../src/roles.service';
import { AuthService } from '../src/auth.service';
describe('Service: AuthService', () => {
let service: AuthService;
const rolesService = { isTechnical: jest.fn(), isAdmin: jest.fn() };
beforeEach(async () => {
service = await configureServiceTest({
service: AuthService,
mocks: [
{
provide: RolesService,
useValue: rolesService,
},
],
});
});
afterEach(() => jest.restoreAllMocks());
it(`should reply with 'ok' if authorization success`, async () => {
rolesService.isTechnical.and.returnValue(true);
rolesService.isAdmin.and.returnValue(false);
const bearer = 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlcyI6W119.0Dd6yUeJ4UbCr8WyXOiK3BhqVVwJFk5c53ipJBWenmc';
const result = service.hasSufficientRole(bearer);
expect(result).toBe(true);
});
});
FAQs
fastify-decorators plugin to work with Sequelize
The npm package @fastify-decorators/simple-di receives a total of 442 weekly downloads. As such, @fastify-decorators/simple-di popularity was classified as not popular.
We found that @fastify-decorators/simple-di demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

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.

Security News
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.

Security News
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.

Security News
Node.js has paused its bug bounty program after funding ended, removing payouts for vulnerability reports but keeping its security process unchanged.