Lazy-loading provider
By default, modules are eagerly loaded, which means that as soon as the application loads, so do all the modules, whether they are immediately necessary. While this is fine for most applications, it may become a bottleneck for apps running in the serverless environment, where the startup latency ("cold start")
is crucial.
Lazy loading can help decrease bootstrap time by loading only modules required by the specific serverless function invocation. In addition, you could also load other modules asynchronously once the serverless function is "warm" to speed up the bootstrap time for subsequent calls even further (deferred modules registration).
Usage
To load a provider on-demand, Ts.ED provides decorators LazyInject and OptionalLazyInject. Here is an example with a PlatformExceptions:
import {Injectable, LazyInject} from "@tsed/di";
import type {PlatformExceptions} from "@tsed/platform-exceptions";
@Injectable()
class MyInjectable {
@LazyInject(() => import("@tsed/platform-exceptions"))
private platformExceptions: Promise<PlatformExceptions>;
async use() {
try {
/// do something
} catch (er) {
const exceptions = await this.platformExceptions;
exceptions.catch(er, {});
}
}
}
import {injectable, lazyInject} from "@tsed/di";
class MyInjectable {
async use() {
try {
/// do something
// Example: Perform some operation that might fail
await someAsyncOperation();
} catch (er) {
const exceptions = await lazyInject(() => import("@tsed/platform-exceptions"));
// Handle the error with appropriate options
exceptions.catch(er, injectContext());
}
}
}
injectable(MyInjectable);
The LazyInject decorator will load the node_module
and invoke automatically the PlatformException
exported class, only when the decorated property will be used by your code.
TIP
Lazy-loaded provider is cached
on the first call. That means, each consecutive call will be very fast and will return a cached instance, instead of loading the provider again.
Creating your own lazy injectable does not require special configuration, just declare a module or an injectable service with default export:
import {Module} from "@tsed/di";
@Module({
// works also with @Injectable
imports: [] // Use the imports field if you have services to build
})
export default class MyModule {
private initialized: boolean = false;
$onInit() {
// This lifecycle hook is called after the module is loaded
// and all dependencies are resolved.
// Example: Initialize module-specific resources
this.initialized = true;
console.log("MyModule initialized!");
}
}
import {injectable} from "@tsed/di";
export default class MyModule {
private initialized: boolean = false;
$onInit() {
// This lifecycle hook is called after the module is loaded
// and all dependencies are resolved.
// Example: Initialize module-specific resources
this.initialized = true;
console.log("MyModule initialized!");
}
}
injectable(MyModule).configuration({
imports: [
// add your services here
// Example: Import required services
// AuthService,
// UserService,
// {
// token: 'CONFIG',
// use: {apiUrl: 'https://api.example.com'}
// }
]
});
Then use it:
import {Injectable, LazyInject} from "@tsed/di";
import type {default as MyModule} from "./MyModule.js";
@Injectable()
class MyInjectable {
@LazyInject(() => import("./MyModule.js"))
private myModule: Promise<MyModule>;
async use() {
const myModule = await this.myModule;
myModule.doSomething();
}
}
import {injectable, lazyInject} from "@tsed/di";
class MyInjectable {
async use() {
const myModule = await lazyInject(() => import("./MyModule.js"));
myModule.doSomething();
}
}
injectable(MyInjectable);
WARNING
If you use Webpack, make sure to update your tsconfig.json
file - setting compilerOptions.module
to "esnext"
and adding compilerOptions.moduleResolution
property with "node"
as a value:
{
"compilerOptions": {
"module": "esnext",
"moduleResolution": "node"
}
}
Limitation
Some providers cannot be lazy loaded:
- Controllers,
- Middlewares,
- All providers that need to run a specific hook (excepted
$onInit
hook).