Providers
In ExpressoTS, providers act as modular components that encapsulate specific functionalities, such as email services, authentication, or database connections. This approach supports a loosely coupled architecture, allowing you to update or swap out these functionalities without impacting the rest of the system.
Key advantages
- Promote loose coupling between application layers.
- Simplify the testing process by decoupling logic from specific implementations.
- Enhance code maintainability and flexibility, supporting easy swaps of underlying services.
ExpressoTS harnesses providers to augment application capabilities, facilitating functionalities like email dispatching, data storage, and more, without tying the application logic to specific implementations.
Create a provider
Consider an application needing to send emails under various circumstances, such as user registration or password recovery. By implementing an email provider, ExpressoTS can send emails without directly interacting with the mailing service in the business logic.
In this example we will create a MailSenderProvider to send emails using the nodemailer library.
Let's use the CLI to scaffold a new provider named MailSenderProvider:
expressots g p mailSender
Setting up the provider
The CLI will add the suffix Provider to the provider name, creating a new file in the providers directory. This file will contain the provider class, which you can then customize to suit your application's needs.
Here is the default provider file generated by the CLI:
import { provide } from "@expressots/core";
@provide(MailSenderProvider)
class MailSenderProvider {}
Implementing the provider
import nodemailer from "nodemailer";
import Mail from "nodemailer/lib/mailer";
export const enum EmailType {
Welcome = 0,
CreateUser,
ChangePassword,
Login,
RecoveryPassword,
}
@provide(MailSenderProvider)
export class MailSenderProvider {
private transporter: Mail;
constructor() {
this.transporter = nodemailer.createTransport({
host: Env.Mailtrap.HOST,
port: Env.Mailtrap.PORT,
auth: {
user: Env.Mailtrap.USERNAME,
pass: Env.Mailtrap.PASSWORD,
},
});
}
private mailSender(message: IMessage): Promise<void> {
await this.transporter.sendMail({
to: {
name: message.to.name,
address: message.to.email,
},
from: {
name: message.from.name,
address: message.from.email,
},
subject: message.subject,
html: message.body,
});
}
sendEmail(emailType: EmailType): Promise<void> {
switch (emailType) {
case EmailType.Login:
break;
case EmailType.Welcome:
break;
case EmailType.RecoveryPassword:
break;
case EmailType.ChangePassword:
break;
case EmailType.CreateUser:
this.MailSender({
to: {
name: "User",
email: Env.Mailtrap.INBOX_ALIAS,
},
from: {
name: "ExpressoTS",
},
subject: "Successfully logged in!",
body: "<h1>Welcome to the system!</h1>",
});
break;
}
}
}
This MailSenderProvider abstracts the complexity of configuring and using nodemailer for email operations,
providing a straightforward method sendEmail to send different types of emails.
Using the provider
Here is the use case implementation making use of the MailSenderProvider provider:
@provide(LoginUserUseCase)
export class LoginUserUseCase {
constructor(private mailSender: MailSenderProvider) {}
execute(payload: ILoginUserRequestDTO): boolean {
const { email, password } = payload;
if (isAuthenticated(email, password)) {
return true;
}
mailSender.sendEmail(EmailType.Login);
return false;
}
}
In this use case, the MailSenderProvider is injected through the constructor, leveraging ExpressoTS's dependency injection.
This decouples the email sending process from the authentication logic, illustrating the provider's role in maintaining clean and maintainable code.
Decorators for provider
ExpressoTS facilitates the registration of providers in its dependency injection system through the use of fluent decorators:
- @provide (default scope is request)
- @provideSingleton (singleton scope)
- @provideTransient (transient scope)
These decorators ensure that providers are automatically registered and resolved within the application's dependency injection system.
External providers
ExpressoTS promotes extensibility through the use of external providers. Developers can create reusable packages using the plugin design pattern to add new functionalities to an ExpressoTS application. This approach is ideal for sharing features across multiple projects or integrating third-party services.
The goal is to keep the core framework lean while enabling developers to extend their applications' capabilities with external providers.
Plugin pattern
The diagram illustrates the ExpressoTS plugin design pattern, demonstrating how external providers are seamlessly integrated into the client application through the Provider Manager.
It showcases the process of registering new providers and their respective lifecycle scopes, which can be singleton, request, or scoped.
