Skip to main content

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.
info

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",
email: "[email protected]",
},
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:

User sending email if login fails
@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.

Plugin pattern

Create external provider

The CLI can be used to scaffold a new external provider ( plugin ). Here's the command:

expressots create provider-name

This command generates a new directory with the provider's name and all the necessary files to start developing an external provider. The generated package.json file includes scripts for building, testing, and publishing the package.

An external provider should be a CommonJS package that meets the following requirements:

  1. Implement the IProvider interface in your provider class.
IProvider interface
export interface IProvider {
name: string;
version: string;
author: string;
repo: string;
}
  1. Annotate your provider class with the @injectable() decorator for dependency injection.
  2. Export your provider class in the index.ts file.
  3. Publish your package to the npm registry.
Example of a Logger provider
import { IProvider } from "expressots";

@injectable()
class Logger implements IProvider {
log(message: string): void {
console.log(message);
}
}

Once the package is developed and published to the npm registry, it can be easily integrated into any ExpressoTS application.

Using external providers

Providers are registered in the App class of an ExpressoTS application using the ProviderManager.

Registering external Logger provider in the App class
@provide(App)
export class App extends AppExpress {
private provider: ProviderManager;

constructor() {
super();
this.provider = container.get(ProviderManager);
}

protected configureServices(): void {
this.provider.register(Logger);
}
}
info

By default provider registration binds the provider as request-scoped. To change the scope, use the BindingScopeEnum or string representation of the scope.

Example of registering a provider as a singleton:

this.provider.register(Logger, "Singleton");

Adding external providers to an ExpressoTS application is a straightforward process that enhances the application's capabilities without bloating the core framework.

External provider best practices

When developing external providers for ExpressoTS, consider the following best practices:

  • Interface Implementation: Ensure that external providers conform to the IProvider interface for consistency.
  • Lifecycle Management: Choose the appropriate scope for the provider based on its intended usage pattern.
  • Testing: Thoroughly test providers in isolation before integrating them into the main application.
  • Documentation: Provide detailed usage instructions and configuration options for the external provider.
  • Versioning: Follow semantic versioning practices to manage changes and updates to the provider.
  • Error Handling: Implement robust error handling and logging mechanisms to ensure the provider's reliability.
  • Security: Follow security best practices to protect sensitive data and prevent vulnerabilities in the provider, especially in the provider dependencies.

By adhering to these practices, developers can ensure that their external providers are robust, maintainable, and easily integrated into any ExpressoTS application.

Support us ❤️

ExpressoTS is an MIT-licensed open source project. It's an independent project with ongoing development made possible thanks to your support. If you'd like to help, please read our support guide.