|
| 1 | +--- |
| 2 | +name: loopback-core |
| 3 | +description: |
| 4 | + Build large-scale, extensible Node.js applications and frameworks using |
| 5 | + LoopBack 4 core patterns. Use when building TypeScript/Node.js projects that |
| 6 | + need IoC containers, dependency injection, extension point/extension patterns, |
| 7 | + interceptors, life cycle observers, or component-based architecture. Triggers |
| 8 | + on tasks involving @loopback/core, @loopback/context, Context, Binding, |
| 9 | + @inject, @injectable, @extensionPoint, @extensions, LifeCycleObserver, |
| 10 | + Interceptor, or Component patterns. Also use when the user asks about |
| 11 | + structuring large-scale Node.js projects for extensibility and composability. |
| 12 | +--- |
| 13 | + |
| 14 | +# Build Large-Scale Node.js Projects with LoopBack 4 Core |
| 15 | + |
| 16 | +LoopBack 4 core provides an IoC container and DI framework in TypeScript |
| 17 | +designed for async-first, large-scale Node.js applications. Import from |
| 18 | +`@loopback/core` (not `@loopback/context`). This skill covers only |
| 19 | +`@loopback/core` — not REST, repositories, or other LoopBack modules. |
| 20 | + |
| 21 | +## Architecture Decision Tree |
| 22 | + |
| 23 | +1. **Need to manage artifacts and their dependencies?** -> Context & Bindings |
| 24 | + ([context-and-bindings.md](references/context-and-bindings.md)) |
| 25 | +2. **Need loose coupling between artifact construction and behavior?** -> |
| 26 | + Dependency Injection |
| 27 | + ([dependency-injection.md](references/dependency-injection.md)) |
| 28 | +3. **Need a pluggable system where others can add capabilities?** -> Extension |
| 29 | + Point/Extension ([extension-points.md](references/extension-points.md)) |
| 30 | +4. **Need cross-cutting concerns (caching, logging, tracing)?** -> Interceptors |
| 31 | + ([interceptors-and-observers.md](references/interceptors-and-observers.md)) |
| 32 | +5. **Need to hook into app start/stop?** -> Life Cycle Observers |
| 33 | + ([interceptors-and-observers.md](references/interceptors-and-observers.md)) |
| 34 | +6. **Need runtime-configurable artifacts?** -> Configuration |
| 35 | + ([configuration.md](references/configuration.md)) |
| 36 | +7. **Need custom decorators, parameterized classes, or advanced patterns?** -> |
| 37 | + Advanced Recipes ([advanced-recipes.md](references/advanced-recipes.md)) |
| 38 | + |
| 39 | +## Quick Start: Minimal Extensible Application |
| 40 | + |
| 41 | +```ts |
| 42 | +import { |
| 43 | + Application, |
| 44 | + BindingKey, |
| 45 | + Component, |
| 46 | + Binding, |
| 47 | + createBindingFromClass, |
| 48 | + extensionPoint, |
| 49 | + extensions, |
| 50 | + BindingTemplate, |
| 51 | + extensionFor, |
| 52 | + injectable, |
| 53 | + Getter, |
| 54 | + config, |
| 55 | +} from '@loopback/core'; |
| 56 | + |
| 57 | +// 1. Define the extension contract |
| 58 | +export interface Greeter { |
| 59 | + language: string; |
| 60 | + greet(name: string): string; |
| 61 | +} |
| 62 | + |
| 63 | +export const GREETER_EXTENSION_POINT_NAME = 'greeters'; |
| 64 | + |
| 65 | +export const asGreeter: BindingTemplate = binding => { |
| 66 | + extensionFor(GREETER_EXTENSION_POINT_NAME)(binding); |
| 67 | + binding.tag({namespace: 'greeters'}); |
| 68 | +}; |
| 69 | + |
| 70 | +// 2. Define the extension point |
| 71 | +@extensionPoint(GREETER_EXTENSION_POINT_NAME) |
| 72 | +export class GreetingService { |
| 73 | + constructor( |
| 74 | + @extensions() private getGreeters: Getter<Greeter[]>, |
| 75 | + @config() public readonly options?: {color: string}, |
| 76 | + ) {} |
| 77 | + async greet(language: string, name: string): Promise<string> { |
| 78 | + const greeters = await this.getGreeters(); |
| 79 | + const greeter = greeters.find(g => g.language === language); |
| 80 | + return greeter ? greeter.greet(name) : `Hello, ${name}!`; |
| 81 | + } |
| 82 | +} |
| 83 | + |
| 84 | +// 3. Implement extensions |
| 85 | +@injectable(asGreeter) |
| 86 | +export class EnglishGreeter implements Greeter { |
| 87 | + language = 'en'; |
| 88 | + greet(name: string) { |
| 89 | + return `Hello, ${name}!`; |
| 90 | + } |
| 91 | +} |
| 92 | + |
| 93 | +@injectable(asGreeter) |
| 94 | +export class ChineseGreeter implements Greeter { |
| 95 | + language = 'zh'; |
| 96 | + greet(name: string) { |
| 97 | + return `${name},你好!`; |
| 98 | + } |
| 99 | +} |
| 100 | + |
| 101 | +// 4. Bundle into a component |
| 102 | +export const GREETING_SERVICE = BindingKey.create<GreetingService>( |
| 103 | + 'services.GreetingService', |
| 104 | +); |
| 105 | + |
| 106 | +export class GreetingComponent implements Component { |
| 107 | + bindings: Binding[] = [ |
| 108 | + createBindingFromClass(GreetingService, {key: GREETING_SERVICE}), |
| 109 | + createBindingFromClass(EnglishGreeter), |
| 110 | + createBindingFromClass(ChineseGreeter), |
| 111 | + ]; |
| 112 | +} |
| 113 | + |
| 114 | +// 5. Compose components via nesting |
| 115 | +export class CoreComponent implements Component { |
| 116 | + // services: auto-registered service/provider classes |
| 117 | + services = [SomeUtilityService]; |
| 118 | +} |
| 119 | + |
| 120 | +export class AppComponent implements Component { |
| 121 | + // components: nested components (registered recursively) |
| 122 | + components = [CoreComponent, GreetingComponent]; |
| 123 | + // services: additional services for this layer |
| 124 | + services = [AppSpecificService]; |
| 125 | +} |
| 126 | + |
| 127 | +// 6. Wire up the application |
| 128 | +export class MyApp extends Application { |
| 129 | + constructor() { |
| 130 | + super({shutdown: {signals: ['SIGINT']}}); |
| 131 | + this.component(AppComponent); |
| 132 | + } |
| 133 | + async main() { |
| 134 | + const svc = await this.get(GREETING_SERVICE); |
| 135 | + console.log(await svc.greet('en', 'World')); |
| 136 | + } |
| 137 | +} |
| 138 | +``` |
| 139 | + |
| 140 | +## Core Patterns Summary |
| 141 | + |
| 142 | +| Pattern | Key APIs | When to Use | |
| 143 | +| ----------------- | --------------------------------------------------------------------------------------------- | --------------------------------------------- | |
| 144 | +| Context & Binding | `Context`, `bind()`, `toClass()`, `toDynamicValue()`, `toProvider()` | Managing artifacts and their dependencies | |
| 145 | +| DI | `@inject()`, `@inject.getter()`, `@inject.view()` | Decoupling construction from behavior | |
| 146 | +| Extension Point | `@extensionPoint()`, `@extensions()`, `@extensions.list()`, `extensionFor()`, `@injectable()` | Pluggable, open-ended feature sets | |
| 147 | +| Interceptor | `@injectable(asGlobalInterceptor())`, `Provider<Interceptor>` | Cross-cutting concerns | |
| 148 | +| Observer | `@lifeCycleObserver('group')`, `LifeCycleObserver` | Startup/shutdown hooks (group controls order) | |
| 149 | +| Configuration | `@config()`, `@config.view()`, `app.configure()` | Runtime-configurable behavior | |
| 150 | +| Component | `Component`, `components[]`, `services[]`, `bindings[]` | Composable packaging of artifacts | |
| 151 | + |
| 152 | +## Key Rules |
| 153 | + |
| 154 | +- Always import from `@loopback/core`, not `@loopback/context` |
| 155 | +- Use `BindingKey.create<T>()` for strongly-typed keys |
| 156 | +- Extension injection: `@extensions()` returns `Getter<T[]>` (lazy, picks up |
| 157 | + dynamic additions); `@extensions.list()` returns `T[]` (eager, simpler when |
| 158 | + extensions are static at startup) |
| 159 | +- Use `@config()` for artifact configuration, `app.configure(key).to(value)` to |
| 160 | + set it |
| 161 | +- Use `@injectable(bindingTemplate)` to decorate extension classes — can combine |
| 162 | + scope and extension: |
| 163 | + `@injectable({scope: BindingScope.SINGLETON}, extensionFor(POINT))` |
| 164 | +- Use `createBindingFromClass()` to create bindings that respect `@injectable` |
| 165 | + metadata |
| 166 | +- Compose components hierarchically: `components[]` for nesting, `services[]` |
| 167 | + for auto-registration, `bindings[]` for custom bindings |
| 168 | +- Use `BindingScope.SINGLETON` for shared stateful services |
| 169 | +- Use `CoreBindings.APPLICATION_INSTANCE` to inject the Application itself |
| 170 | +- Use `ContextTags.KEY` to tag bindings with a stable key: |
| 171 | + `tags: {[ContextTags.KEY]: MY_KEY}` |
| 172 | +- Lifecycle observer groups are sorted alphabetically — use numbered prefixes |
| 173 | + (e.g., `'03-setup'`, `'10-app'`) to control startup order |
| 174 | + |
| 175 | +## References |
| 176 | + |
| 177 | +- **Context & Bindings**: |
| 178 | + [references/context-and-bindings.md](references/context-and-bindings.md) — |
| 179 | + creating contexts, binding types, scopes, finding bindings, context hierarchy, |
| 180 | + views, components |
| 181 | +- **Dependency Injection**: |
| 182 | + [references/dependency-injection.md](references/dependency-injection.md) — |
| 183 | + constructor/property/method injection, getters, views, custom decorators, |
| 184 | + custom injectors |
| 185 | +- **Extension Points**: |
| 186 | + [references/extension-points.md](references/extension-points.md) — defining |
| 187 | + contracts, extension point classes, implementing/registering extensions, |
| 188 | + configuration |
| 189 | +- **Interceptors & Observers**: |
| 190 | + [references/interceptors-and-observers.md](references/interceptors-and-observers.md) |
| 191 | + — global interceptors, interceptor proxies, life cycle observers, dynamic |
| 192 | + config via ContextView |
| 193 | +- **Configuration**: [references/configuration.md](references/configuration.md) |
| 194 | + — `@config()`, `@config.view()`, dynamic config, custom resolvers, sync vs |
| 195 | + async |
| 196 | +- **Advanced Recipes**: |
| 197 | + [references/advanced-recipes.md](references/advanced-recipes.md) — custom |
| 198 | + decorators, custom injectors, parameterized class factories, application |
| 199 | + scaffolding |
0 commit comments