CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-loopback--core

Define and implement core constructs such as Application and Component for LoopBack 4 framework

Pending
Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Pending

The risk profile of this skill

Overview
Eval results
Files

services.mddocs/

Service Layer

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.

Capabilities

Service Injection Decorator

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
  ) {}
}

Service Binding Creation

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')));

Service Interface Types

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'
    });
  }
}

Service Filtering

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))
    );
  }
}

Provider Pattern Integration

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);

Service Composition

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 dependencies

Service Lifecycle Integration

How 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);

Dynamic Service Discovery

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);

Types

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;
}

docs

application.md

components.md

context-api.md

extensions.md

index.md

lifecycle.md

services.md

tile.json