Automatic dependency injection using TypeScript decorators and reflection metadata for seamless constructor and method parameter resolution. AdonisJS Fold uses reflection to discover dependencies and inject them automatically.
TypeScript decorator for automatic dependency injection using reflection metadata.
/**
* The @inject decorator uses Reflection to inspect the dependencies of a class
* or a method and defines them as metadata on the class for the container to
* discover them.
*/
function inject(): <C extends Function>(target: C) => void;
function inject(): (target: any, propertyKey: string | symbol) => void;Usage Examples:
import { inject, Container } from "@adonisjs/fold";
// Constructor injection
@inject()
class UserService {
constructor(
private logger: Logger,
private database: Database
) {}
async getUsers() {
this.logger.info("Fetching users");
return this.database.query("SELECT * FROM users");
}
}
// Method injection
class UserController {
@inject()
async index(logger: Logger, userService: UserService) {
logger.info("User index endpoint called");
return userService.getUsers();
}
@inject()
async show(userService: UserService, request: Request) {
const id = request.param("id");
return userService.findById(id);
}
}
const container = new Container();
const userService = await container.make(UserService);
const controller = new UserController();
const users = await container.call(controller, "index");The inject decorator relies on TypeScript's reflect-metadata for parameter type discovery.
Setup Requirements:
// Install reflect-metadata
npm install reflect-metadata
// Import at the top of your entry file
import "reflect-metadata";
// Enable decorators in tsconfig.json
{
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
}Classes with the @inject() decorator become automatically resolvable by the container.
/**
* Shape of a class constructor with injections
*/
type InspectableConstructor = Function & {
containerInjections?: Record<
string | number | symbol,
{
dependencies: any[];
createError?: ErrorCreator;
}
>;
containerProvider?: ContainerProvider;
};Usage Examples:
import { inject, Container } from "@adonisjs/fold";
// Simple injection
@inject()
class EmailService {
constructor(private config: Config) {}
async send(to: string, message: string) {
// Implementation using this.config
}
}
// Multiple dependencies
@inject()
class UserService {
constructor(
private database: Database,
private logger: Logger,
private emailService: EmailService
) {}
async createUser(userData: any) {
this.logger.info("Creating new user");
const user = await this.database.insert("users", userData);
await this.emailService.send(user.email, "Welcome!");
return user;
}
}
const container = new Container();
// Register dependencies
container.bindValue("config", new Config());
container.singleton("database", () => new Database());
container.singleton("logger", () => new Logger());
// UserService and EmailService will be resolved automatically
const userService = await container.make(UserService);Different patterns for using method injection with the @inject() decorator.
Usage Examples:
import { inject, Container } from "@adonisjs/fold";
class ApiController {
// Basic method injection
@inject()
async getUsers(userService: UserService, logger: Logger) {
logger.info("Fetching all users");
return userService.getAll();
}
// Mixed injection with regular parameters
@inject()
async getUserById(userService: UserService, id: string) {
return userService.findById(id);
}
// Complex method injection
@inject()
async createUser(
userService: UserService,
validator: Validator,
emailService: EmailService,
userData: any
) {
const validatedData = await validator.validate(userData);
const user = await userService.create(validatedData);
await emailService.send(user.email, "Welcome!");
return user;
}
}
const container = new Container();
const controller = new ApiController();
// Call methods with dependency injection
// Container resolves UserService and Logger automatically
const users = await container.call(controller, "getUsers");
// Pass runtime values for regular parameters
const user = await container.call(controller, "getUserById", ["123"]);
// Mix injected dependencies with runtime values
const newUser = await container.call(controller, "createUser", [userData]);Custom error handling for dependency injection failures.
Usage Examples:
import { inject, Container } from "@adonisjs/fold";
import { RuntimeException } from "@poppinss/utils/exception";
@inject()
class UserService {
constructor(private database: Database) {}
}
const container = new Container();
// Custom error creator for better debugging
const createError = (message: string) => {
const error = new RuntimeException(`UserService: ${message}`);
error.stack = new Error().stack;
return error;
};
try {
const userService = await container.make(UserService, [], createError);
} catch (error) {
console.error("Failed to create UserService:", error.message);
}Advanced customization of the dependency resolution process.
/**
* The container provider to discover and build dependencies
* for the constructor or the class method.
*/
type ContainerProvider = (
binding: InspectableConstructor,
property: string | symbol | number,
resolver: ContainerResolver<any>,
defaultProvider: DefaultContainerProvider,
runtimeValues?: any[]
) => Promise<any[]>;
/**
* The default implementation of the container provider.
*/
type DefaultContainerProvider = (
binding: InspectableConstructor,
property: string | symbol | number,
resolver: ContainerResolver<any>,
runtimeValues?: any[]
) => Promise<any[]>;Usage Examples:
import { inject, Container } from "@adonisjs/fold";
// Custom container provider for specific classes
@inject()
class CustomService {
constructor(private logger: Logger) {}
static containerProvider = async (
binding: any,
property: string | symbol | number,
resolver: any,
defaultProvider: any,
runtimeValues?: any[]
) => {
// Custom logic for resolving dependencies
if (property === '_constructor') {
return [new CustomLogger()];
}
return defaultProvider(binding, property, resolver, runtimeValues);
};
}
const container = new Container();
const service = await container.make(CustomService);