Define and implement core constructs such as Application and Component for LoopBack 4 framework
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
The Service Layer in LoopBack Core provides a robust dependency injection system for registering and consuming services throughout the application. It supports both service classes and provider patterns with type-safe service interfaces and automatic discovery.
Main decorator for injecting services by interface, with automatic type inference and service discovery.
/**
* @service injects a service instance that matches the class or interface.
*/
function service(
serviceInterface?: ServiceInterface,
metadata?: InjectionMetadata
): PropertyDecorator & ParameterDecorator;Usage Examples:
import { service, inject } from "@loopback/core";
// Service class
class EmailService {
async sendEmail(to: string, subject: string, body: string): Promise<void> {
// Email implementation
}
}
// Service interface (using symbol)
const LOGGER_SERVICE = Symbol('LoggerService');
interface LoggerService {
log(message: string): void;
error(message: string, error?: Error): void;
}
// Using @service decorator in controllers
class UserController {
constructor(
@service(EmailService) private emailService: EmailService,
@service(LOGGER_SERVICE) private logger: LoggerService
) {}
@service() // Type inferred from property type
private validationService: ValidationService;
async createUser(userData: UserData): Promise<User> {
this.logger.log('Creating new user');
// Use injected services
const user = await this.userRepository.create(userData);
await this.emailService.sendEmail(
user.email,
'Welcome!',
'Welcome to our platform'
);
return user;
}
}
// Service injection with metadata
class OrderController {
constructor(
@service(PaymentService, {optional: true})
private paymentService?: PaymentService
) {}
}Functions for creating service bindings with proper configuration and interface mapping.
/**
* Create a service binding from a class or provider
*/
function createServiceBinding<S>(
cls: ServiceOrProviderClass<S>,
options?: ServiceOptions
): Binding<S>;
/**
* Create a binding template for a service interface
*/
function asService(serviceInterface: ServiceInterface): BindingTemplate;Usage Examples:
import {
createServiceBinding,
asService,
ServiceOptions,
Application
} from "@loopback/core";
// Service class
class NotificationService {
async notify(message: string): Promise<void> {
console.log('Notification:', message);
}
}
// Provider class
class ConfigServiceProvider implements Provider<ConfigService> {
value(): ConfigService {
return new ConfigService({
env: process.env.NODE_ENV || 'development',
apiUrl: process.env.API_URL || 'http://localhost:3000'
});
}
}
const app = new Application();
// Create service binding manually
const notificationBinding = createServiceBinding(NotificationService);
app.add(notificationBinding);
// Create service binding with options
const configBinding = createServiceBinding(ConfigServiceProvider, {
name: 'app-config',
interface: Symbol.for('ConfigService')
});
app.add(configBinding);
// Apply service template
const loggerBinding = app.bind('services.logger')
.toClass(LoggerService)
.apply(asService(Symbol.for('LoggerService')));Type definitions for service interfaces and discovery mechanisms.
/**
* Representing an interface for services. In TypeScript, the interface does
* not have reflections at runtime. We use a string, a symbol or a Function as
* the type for the service interface.
*/
type ServiceInterface = string | symbol | Function;
/**
* Options to register a service binding
*/
interface ServiceOptions extends BindingFromClassOptions {
interface?: ServiceInterface;
}Usage Examples:
import { ServiceInterface, ServiceOptions } from "@loopback/core";
// Different service interface types
const stringInterface: ServiceInterface = 'UserService';
const symbolInterface: ServiceInterface = Symbol.for('PaymentService');
const classInterface: ServiceInterface = EmailService;
// Service registration with different interfaces
class ServiceManager {
registerServices(app: Application): void {
// Register with class as interface
app.service(EmailService, {
interface: EmailService
});
// Register with symbol interface
app.service(PaymentServiceProvider, {
interface: Symbol.for('PaymentService'),
name: 'payment-processor'
});
// Register with string interface
app.service(LoggerService, {
interface: 'services.logger'
});
}
}Function for filtering bindings by service interface to support service discovery.
/**
* Create a binding filter by service class
*/
function filterByServiceInterface(
serviceInterface: ServiceInterface
): BindingFilter;Usage Examples:
import { filterByServiceInterface, ContextView } from "@loopback/core";
// Service interface
const NOTIFICATION_SERVICE = Symbol.for('NotificationService');
class NotificationManager {
constructor(
@inject.context() private context: Context
) {}
async getAllNotificationServices(): Promise<NotificationService[]> {
// Filter bindings by service interface
const filter = filterByServiceInterface(NOTIFICATION_SERVICE);
const view = new ContextView(this.context, filter);
return view.values();
}
async sendToAllServices(message: string): Promise<void> {
const services = await this.getAllNotificationServices();
await Promise.all(
services.map(service => service.notify(message))
);
}
}How services integrate with the Provider pattern for dynamic value creation.
Usage Examples:
import { Provider, service, ServiceOptions } from "@loopback/core";
// Configuration interface
interface DatabaseConfig {
host: string;
port: number;
database: string;
ssl: boolean;
}
// Provider for database configuration
class DatabaseConfigProvider implements Provider<DatabaseConfig> {
value(): DatabaseConfig {
return {
host: process.env.DB_HOST || 'localhost',
port: parseInt(process.env.DB_PORT || '5432'),
database: process.env.DB_NAME || 'myapp',
ssl: process.env.NODE_ENV === 'production'
};
}
}
// Service that uses the configuration
class DatabaseService {
constructor(
@service('database.config') private config: DatabaseConfig
) {}
async connect(): Promise<void> {
console.log(`Connecting to ${this.config.host}:${this.config.port}/${this.config.database}`);
// Database connection logic
}
}
// Registration
const app = new Application();
// Register config provider
app.service(DatabaseConfigProvider, {
name: 'database.config',
interface: 'database.config'
});
// Register database service
app.service(DatabaseService);How to compose complex services from multiple dependencies.
Usage Examples:
import { service, inject } from "@loopback/core";
// Individual services
class EmailService {
async send(to: string, subject: string, body: string): Promise<void> {
// Email implementation
}
}
class SmsService {
async send(to: string, message: string): Promise<void> {
// SMS implementation
}
}
class PushNotificationService {
async send(userId: string, message: string): Promise<void> {
// Push notification implementation
}
}
// Composite service using multiple dependencies
class NotificationService {
constructor(
@service(EmailService) private emailService: EmailService,
@service(SmsService) private smsService: SmsService,
@service(PushNotificationService) private pushService: PushNotificationService,
@inject('config.notifications') private config: NotificationConfig
) {}
async notifyUser(userId: string, message: NotificationMessage): Promise<void> {
const user = await this.getUserById(userId);
// Send via multiple channels based on configuration
if (this.config.enableEmail && user.email) {
await this.emailService.send(user.email, message.subject, message.body);
}
if (this.config.enableSms && user.phone) {
await this.smsService.send(user.phone, message.text);
}
if (this.config.enablePush) {
await this.pushService.send(userId, message.text);
}
}
private async getUserById(userId: string): Promise<User> {
// User lookup implementation
return {} as User;
}
}
// Register all services
const app = new Application();
app.service(EmailService);
app.service(SmsService);
app.service(PushNotificationService);
app.service(NotificationService); // Will receive all dependenciesHow services integrate with the application lifecycle system.
Usage Examples:
import {
service,
lifeCycleObserver,
LifeCycleObserver,
inject
} from "@loopback/core";
// Service that implements lifecycle observer
@lifeCycleObserver('external-services')
class ExternalApiService implements LifeCycleObserver {
private client: ApiClient | null = null;
constructor(
@inject('config.external-api') private config: ExternalApiConfig
) {}
async init(): Promise<void> {
console.log('Initializing external API client...');
this.client = new ApiClient(this.config);
}
async start(): Promise<void> {
console.log('Connecting to external API...');
await this.client?.connect();
}
async stop(): Promise<void> {
console.log('Disconnecting from external API...');
await this.client?.disconnect();
this.client = null;
}
async fetchData(endpoint: string): Promise<any> {
if (!this.client) {
throw new Error('API client not initialized');
}
return this.client.get(endpoint);
}
}
// Controller using the lifecycle-aware service
class DataController {
constructor(
@service(ExternalApiService) private apiService: ExternalApiService
) {}
async getData(): Promise<any> {
return this.apiService.fetchData('/api/data');
}
}
// Register both service and controller
const app = new Application();
app.service(ExternalApiService); // Will be managed by lifecycle system
app.controller(DataController);How to dynamically discover and work with multiple services of the same interface.
Usage Examples:
import {
service,
extensions,
extensionPoint,
filterByServiceInterface,
ContextView
} from "@loopback/core";
// Plugin interface
interface PaymentProcessor {
processPayment(amount: number, method: string): Promise<PaymentResult>;
getSupportedMethods(): string[];
}
// Multiple implementations
class StripePaymentProcessor implements PaymentProcessor {
async processPayment(amount: number, method: string): Promise<PaymentResult> {
// Stripe implementation
return { success: true, transactionId: 'stripe_123' };
}
getSupportedMethods(): string[] {
return ['card', 'bank_transfer'];
}
}
class PayPalPaymentProcessor implements PaymentProcessor {
async processPayment(amount: number, method: string): Promise<PaymentResult> {
// PayPal implementation
return { success: true, transactionId: 'paypal_456' };
}
getSupportedMethods(): string[] {
return ['paypal', 'credit'];
}
}
// Service that uses all payment processors
const PAYMENT_PROCESSOR = Symbol.for('PaymentProcessor');
class PaymentService {
constructor(
@inject.context() private context: Context
) {}
async getAvailableProcessors(): Promise<PaymentProcessor[]> {
const filter = filterByServiceInterface(PAYMENT_PROCESSOR);
const view = new ContextView<PaymentProcessor>(this.context, filter);
return view.values();
}
async processPayment(amount: number, method: string): Promise<PaymentResult> {
const processors = await this.getAvailableProcessors();
for (const processor of processors) {
if (processor.getSupportedMethods().includes(method)) {
return processor.processPayment(amount, method);
}
}
throw new Error(`No processor found for method: ${method}`);
}
}
// Register all services
const app = new Application();
app.service(StripePaymentProcessor, {
interface: PAYMENT_PROCESSOR
});
app.service(PayPalPaymentProcessor, {
interface: PAYMENT_PROCESSOR
});
app.service(PaymentService);type ServiceInterface = string | symbol | Function;
interface ServiceOptions extends BindingFromClassOptions {
interface?: ServiceInterface;
}
type ServiceOrProviderClass<T = any> =
| Constructor<T | Provider<T>>
| DynamicValueProviderClass<T>;
interface InjectionMetadata {
optional?: boolean;
asProxyWithInterceptors?: boolean;
bindingComparator?: BindingComparator;
}