0
# Service Layer
1
2
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.
3
4
## Capabilities
5
6
### Service Injection Decorator
7
8
Main decorator for injecting services by interface, with automatic type inference and service discovery.
9
10
```typescript { .api }
11
/**
12
* @service injects a service instance that matches the class or interface.
13
*/
14
function service(
15
serviceInterface?: ServiceInterface,
16
metadata?: InjectionMetadata
17
): PropertyDecorator & ParameterDecorator;
18
```
19
20
**Usage Examples:**
21
22
```typescript
23
import { service, inject } from "@loopback/core";
24
25
// Service class
26
class EmailService {
27
async sendEmail(to: string, subject: string, body: string): Promise<void> {
28
// Email implementation
29
}
30
}
31
32
// Service interface (using symbol)
33
const LOGGER_SERVICE = Symbol('LoggerService');
34
35
interface LoggerService {
36
log(message: string): void;
37
error(message: string, error?: Error): void;
38
}
39
40
// Using @service decorator in controllers
41
class UserController {
42
constructor(
43
@service(EmailService) private emailService: EmailService,
44
@service(LOGGER_SERVICE) private logger: LoggerService
45
) {}
46
47
@service() // Type inferred from property type
48
private validationService: ValidationService;
49
50
async createUser(userData: UserData): Promise<User> {
51
this.logger.log('Creating new user');
52
53
// Use injected services
54
const user = await this.userRepository.create(userData);
55
await this.emailService.sendEmail(
56
user.email,
57
'Welcome!',
58
'Welcome to our platform'
59
);
60
61
return user;
62
}
63
}
64
65
// Service injection with metadata
66
class OrderController {
67
constructor(
68
@service(PaymentService, {optional: true})
69
private paymentService?: PaymentService
70
) {}
71
}
72
```
73
74
### Service Binding Creation
75
76
Functions for creating service bindings with proper configuration and interface mapping.
77
78
```typescript { .api }
79
/**
80
* Create a service binding from a class or provider
81
*/
82
function createServiceBinding<S>(
83
cls: ServiceOrProviderClass<S>,
84
options?: ServiceOptions
85
): Binding<S>;
86
87
/**
88
* Create a binding template for a service interface
89
*/
90
function asService(serviceInterface: ServiceInterface): BindingTemplate;
91
```
92
93
**Usage Examples:**
94
95
```typescript
96
import {
97
createServiceBinding,
98
asService,
99
ServiceOptions,
100
Application
101
} from "@loopback/core";
102
103
// Service class
104
class NotificationService {
105
async notify(message: string): Promise<void> {
106
console.log('Notification:', message);
107
}
108
}
109
110
// Provider class
111
class ConfigServiceProvider implements Provider<ConfigService> {
112
value(): ConfigService {
113
return new ConfigService({
114
env: process.env.NODE_ENV || 'development',
115
apiUrl: process.env.API_URL || 'http://localhost:3000'
116
});
117
}
118
}
119
120
const app = new Application();
121
122
// Create service binding manually
123
const notificationBinding = createServiceBinding(NotificationService);
124
app.add(notificationBinding);
125
126
// Create service binding with options
127
const configBinding = createServiceBinding(ConfigServiceProvider, {
128
name: 'app-config',
129
interface: Symbol.for('ConfigService')
130
});
131
app.add(configBinding);
132
133
// Apply service template
134
const loggerBinding = app.bind('services.logger')
135
.toClass(LoggerService)
136
.apply(asService(Symbol.for('LoggerService')));
137
```
138
139
### Service Interface Types
140
141
Type definitions for service interfaces and discovery mechanisms.
142
143
```typescript { .api }
144
/**
145
* Representing an interface for services. In TypeScript, the interface does
146
* not have reflections at runtime. We use a string, a symbol or a Function as
147
* the type for the service interface.
148
*/
149
type ServiceInterface = string | symbol | Function;
150
151
/**
152
* Options to register a service binding
153
*/
154
interface ServiceOptions extends BindingFromClassOptions {
155
interface?: ServiceInterface;
156
}
157
```
158
159
**Usage Examples:**
160
161
```typescript
162
import { ServiceInterface, ServiceOptions } from "@loopback/core";
163
164
// Different service interface types
165
const stringInterface: ServiceInterface = 'UserService';
166
const symbolInterface: ServiceInterface = Symbol.for('PaymentService');
167
const classInterface: ServiceInterface = EmailService;
168
169
// Service registration with different interfaces
170
class ServiceManager {
171
registerServices(app: Application): void {
172
// Register with class as interface
173
app.service(EmailService, {
174
interface: EmailService
175
});
176
177
// Register with symbol interface
178
app.service(PaymentServiceProvider, {
179
interface: Symbol.for('PaymentService'),
180
name: 'payment-processor'
181
});
182
183
// Register with string interface
184
app.service(LoggerService, {
185
interface: 'services.logger'
186
});
187
}
188
}
189
```
190
191
### Service Filtering
192
193
Function for filtering bindings by service interface to support service discovery.
194
195
```typescript { .api }
196
/**
197
* Create a binding filter by service class
198
*/
199
function filterByServiceInterface(
200
serviceInterface: ServiceInterface
201
): BindingFilter;
202
```
203
204
**Usage Examples:**
205
206
```typescript
207
import { filterByServiceInterface, ContextView } from "@loopback/core";
208
209
// Service interface
210
const NOTIFICATION_SERVICE = Symbol.for('NotificationService');
211
212
class NotificationManager {
213
constructor(
214
@inject.context() private context: Context
215
) {}
216
217
async getAllNotificationServices(): Promise<NotificationService[]> {
218
// Filter bindings by service interface
219
const filter = filterByServiceInterface(NOTIFICATION_SERVICE);
220
const view = new ContextView(this.context, filter);
221
return view.values();
222
}
223
224
async sendToAllServices(message: string): Promise<void> {
225
const services = await this.getAllNotificationServices();
226
await Promise.all(
227
services.map(service => service.notify(message))
228
);
229
}
230
}
231
```
232
233
### Provider Pattern Integration
234
235
How services integrate with the Provider pattern for dynamic value creation.
236
237
**Usage Examples:**
238
239
```typescript
240
import { Provider, service, ServiceOptions } from "@loopback/core";
241
242
// Configuration interface
243
interface DatabaseConfig {
244
host: string;
245
port: number;
246
database: string;
247
ssl: boolean;
248
}
249
250
// Provider for database configuration
251
class DatabaseConfigProvider implements Provider<DatabaseConfig> {
252
value(): DatabaseConfig {
253
return {
254
host: process.env.DB_HOST || 'localhost',
255
port: parseInt(process.env.DB_PORT || '5432'),
256
database: process.env.DB_NAME || 'myapp',
257
ssl: process.env.NODE_ENV === 'production'
258
};
259
}
260
}
261
262
// Service that uses the configuration
263
class DatabaseService {
264
constructor(
265
@service('database.config') private config: DatabaseConfig
266
) {}
267
268
async connect(): Promise<void> {
269
console.log(`Connecting to ${this.config.host}:${this.config.port}/${this.config.database}`);
270
// Database connection logic
271
}
272
}
273
274
// Registration
275
const app = new Application();
276
277
// Register config provider
278
app.service(DatabaseConfigProvider, {
279
name: 'database.config',
280
interface: 'database.config'
281
});
282
283
// Register database service
284
app.service(DatabaseService);
285
```
286
287
### Service Composition
288
289
How to compose complex services from multiple dependencies.
290
291
**Usage Examples:**
292
293
```typescript
294
import { service, inject } from "@loopback/core";
295
296
// Individual services
297
class EmailService {
298
async send(to: string, subject: string, body: string): Promise<void> {
299
// Email implementation
300
}
301
}
302
303
class SmsService {
304
async send(to: string, message: string): Promise<void> {
305
// SMS implementation
306
}
307
}
308
309
class PushNotificationService {
310
async send(userId: string, message: string): Promise<void> {
311
// Push notification implementation
312
}
313
}
314
315
// Composite service using multiple dependencies
316
class NotificationService {
317
constructor(
318
@service(EmailService) private emailService: EmailService,
319
@service(SmsService) private smsService: SmsService,
320
@service(PushNotificationService) private pushService: PushNotificationService,
321
@inject('config.notifications') private config: NotificationConfig
322
) {}
323
324
async notifyUser(userId: string, message: NotificationMessage): Promise<void> {
325
const user = await this.getUserById(userId);
326
327
// Send via multiple channels based on configuration
328
if (this.config.enableEmail && user.email) {
329
await this.emailService.send(user.email, message.subject, message.body);
330
}
331
332
if (this.config.enableSms && user.phone) {
333
await this.smsService.send(user.phone, message.text);
334
}
335
336
if (this.config.enablePush) {
337
await this.pushService.send(userId, message.text);
338
}
339
}
340
341
private async getUserById(userId: string): Promise<User> {
342
// User lookup implementation
343
return {} as User;
344
}
345
}
346
347
// Register all services
348
const app = new Application();
349
app.service(EmailService);
350
app.service(SmsService);
351
app.service(PushNotificationService);
352
app.service(NotificationService); // Will receive all dependencies
353
```
354
355
### Service Lifecycle Integration
356
357
How services integrate with the application lifecycle system.
358
359
**Usage Examples:**
360
361
```typescript
362
import {
363
service,
364
lifeCycleObserver,
365
LifeCycleObserver,
366
inject
367
} from "@loopback/core";
368
369
// Service that implements lifecycle observer
370
@lifeCycleObserver('external-services')
371
class ExternalApiService implements LifeCycleObserver {
372
private client: ApiClient | null = null;
373
374
constructor(
375
@inject('config.external-api') private config: ExternalApiConfig
376
) {}
377
378
async init(): Promise<void> {
379
console.log('Initializing external API client...');
380
this.client = new ApiClient(this.config);
381
}
382
383
async start(): Promise<void> {
384
console.log('Connecting to external API...');
385
await this.client?.connect();
386
}
387
388
async stop(): Promise<void> {
389
console.log('Disconnecting from external API...');
390
await this.client?.disconnect();
391
this.client = null;
392
}
393
394
async fetchData(endpoint: string): Promise<any> {
395
if (!this.client) {
396
throw new Error('API client not initialized');
397
}
398
return this.client.get(endpoint);
399
}
400
}
401
402
// Controller using the lifecycle-aware service
403
class DataController {
404
constructor(
405
@service(ExternalApiService) private apiService: ExternalApiService
406
) {}
407
408
async getData(): Promise<any> {
409
return this.apiService.fetchData('/api/data');
410
}
411
}
412
413
// Register both service and controller
414
const app = new Application();
415
app.service(ExternalApiService); // Will be managed by lifecycle system
416
app.controller(DataController);
417
```
418
419
### Dynamic Service Discovery
420
421
How to dynamically discover and work with multiple services of the same interface.
422
423
**Usage Examples:**
424
425
```typescript
426
import {
427
service,
428
extensions,
429
extensionPoint,
430
filterByServiceInterface,
431
ContextView
432
} from "@loopback/core";
433
434
// Plugin interface
435
interface PaymentProcessor {
436
processPayment(amount: number, method: string): Promise<PaymentResult>;
437
getSupportedMethods(): string[];
438
}
439
440
// Multiple implementations
441
class StripePaymentProcessor implements PaymentProcessor {
442
async processPayment(amount: number, method: string): Promise<PaymentResult> {
443
// Stripe implementation
444
return { success: true, transactionId: 'stripe_123' };
445
}
446
447
getSupportedMethods(): string[] {
448
return ['card', 'bank_transfer'];
449
}
450
}
451
452
class PayPalPaymentProcessor implements PaymentProcessor {
453
async processPayment(amount: number, method: string): Promise<PaymentResult> {
454
// PayPal implementation
455
return { success: true, transactionId: 'paypal_456' };
456
}
457
458
getSupportedMethods(): string[] {
459
return ['paypal', 'credit'];
460
}
461
}
462
463
// Service that uses all payment processors
464
const PAYMENT_PROCESSOR = Symbol.for('PaymentProcessor');
465
466
class PaymentService {
467
constructor(
468
@inject.context() private context: Context
469
) {}
470
471
async getAvailableProcessors(): Promise<PaymentProcessor[]> {
472
const filter = filterByServiceInterface(PAYMENT_PROCESSOR);
473
const view = new ContextView<PaymentProcessor>(this.context, filter);
474
return view.values();
475
}
476
477
async processPayment(amount: number, method: string): Promise<PaymentResult> {
478
const processors = await this.getAvailableProcessors();
479
480
for (const processor of processors) {
481
if (processor.getSupportedMethods().includes(method)) {
482
return processor.processPayment(amount, method);
483
}
484
}
485
486
throw new Error(`No processor found for method: ${method}`);
487
}
488
}
489
490
// Register all services
491
const app = new Application();
492
493
app.service(StripePaymentProcessor, {
494
interface: PAYMENT_PROCESSOR
495
});
496
497
app.service(PayPalPaymentProcessor, {
498
interface: PAYMENT_PROCESSOR
499
});
500
501
app.service(PaymentService);
502
```
503
504
## Types
505
506
```typescript { .api }
507
type ServiceInterface = string | symbol | Function;
508
509
interface ServiceOptions extends BindingFromClassOptions {
510
interface?: ServiceInterface;
511
}
512
513
type ServiceOrProviderClass<T = any> =
514
| Constructor<T | Provider<T>>
515
| DynamicValueProviderClass<T>;
516
517
interface InjectionMetadata {
518
optional?: boolean;
519
asProxyWithInterceptors?: boolean;
520
bindingComparator?: BindingComparator;
521
}
522
```