NestJS Shared Modules - nestjs

I am reading Modules in NestJS official documentation and I wanted to try shared modules.
So, I have the following structure for my application:
src
├── cats
│ ├── cats.controller.ts
| ├── cats.module.ts
| └── cats.service.ts
├── users
│ ├── users.controller.ts
| ├── users.module.ts
| └── users.service.ts
├── app.controller.ts
├── app.module.ts
├── app.service.ts
└── main.ts
I understand that modules are singletons and shared modules by default in NestJS so long as you export its service and import its module to another module. Read it here.
On my end, I want to see whether it is true and so I did not follow the documentation and instead imported directly the cats.service.ts into another service called users.service.ts, like the below:
src/users/users.controller.ts
import { Controller, Get } from '#nestjs/common';
import { UsersService } from './user.service';
#Controller('users')
export class UserController {
constructor(private readonly usersService: UsersService) {}
#Get('get-cats')
getCats() {
return this.usersService.getCats();
}
}
src/users/users.service.ts, I imported it here directly.
import { Inject, Injectable } from '#nestjs/common';
import { CatsService } from 'src/cats/cats.service.ts';
#Injectable()
export class UsersService {
getCats() {
return new CatsService().findAll();
}
}
However, it seems that it works fine because I can actually see all the cats when I go to the path localhost:3000/users/get-cats.
I am expecting to get something like must import x inside y to access this service or like any error that prevents me from doing so. Is there something that I missed or did not understand?
Edit:
What I did not follow is this particular line in the official documentation:
In order to do that, we first need to export the CatsService provider by adding it to the module's exports array. Now any module that imports the CatsModule has access to the CatsService and will share the same instance with all other modules that import it as well.

You are right, that is totally possible. But the question is whether it is a good idea to do this?
You basically just created a new instance of the CatsService class. Of course you can do that. There is (obviously) nothing to stop you from doing this plain Typescript logic. Nevertheless CatsService will not be a singleton which can be injected in other services. You have created your own instance and do not benefit from dependency injection (provided by NestJS) anymore.
What happens if you want to pass dependencies to your CatsService constructor? You would have to pass them also in the place where you are creating the CatsService yourself (new CatsService(...dependencies)).
Basically NestJS can be seen as a framework in order to handle dependencies based on the annotations passed to classes. You are free to just ignore the annotations and handle dependency injection yourself. Nevertheless this would result in getting rid of the handy features NestJS provides. This way you would also need to handle the logic of creating singletons as well (if you want to ensure that these classes are instantiated as singletons).
Example:
Calling new CatsService() multiple times in your application would lead to many instances of this class being created. Passing the service to your constructor like constructor(catsService: CatsService) would lead to one instance being created and then this single instance will be injected into different other objects.

Related

(webpack) How to import webpack bundled modules as one instance in all require

I am implementing server-side rendering using react and express. So, I am using the context of react in Express.
However, the problem is that every time the context below is requeried, a new context is created and the context cannot be shared by other modules.
import { createContext, ReactElement } from "react";
type State = {
main: ReactElement | null;
srcList: Array<string> | null;
};
export default class Context {
static HtmlContext = createContext<State>({
main: null,
srcList: [],
});
}
What I wanna know is how to import webpack bundled modules as one instance in all require.
Similar to the singleton pattern, I was trying to find a webpack configuration that allows only one instance to be shared. And I was trying to use optimization option with runtimeChunk: 'single'. But it didn't work well and I don't know if I understood it well...
I would be very grateful if you could learn how to approach it.

How to import an Interface from a dependency of a dependency

Simple question, we have a custom package that uses axios as a dependency and exports a function that returns an Axios Client with some custom default configuration. The problem is that I want the AxiosInstance interface that is not exported from the package itself and axios it's not a dependency in my project.
// #myCustomDep
import { AxiosInstance } from 'axios';
export function createAxiosClient(): AxiosInstance { //... }
// Main project
import { createAxiosClient } from '#myCustomDep';
// Err: Cannot find name 'AxiosInstance'.
function buildClient(): AxiosInstance {
const axiosClient = createAxiosClient();
return axiosClient;
}
What would be the correct way of handling this import without modifying the underlying dependency (if possible)?
As long as it is a dependency of your dependency, you may very well also list it as your own dependency.
But even without doing so, you can still directly import from a transitive dependency.
TypeScript will only complain if it cannot find it in your node_modules, whether you have listed it as a dependency or not.
You can also use TypeScript utility types: ReturnType<typeof createAxiosClient>
https://www.typescriptlang.org/docs/handbook/utility-types.html#returntypetype
Obviously, if possible, you would have your custom dependency re-export whatever is exposed by its API.

Importing typescript definitions for my bundled javascript library

I have a library I wrote in typescript, it contains multiple files and an index.ts file that contains all the exports of the library.
I used webpack to compile the entire library into a single index.js file but I'm having trouble importing it WITH type definitions.
Lets say this is my library:
src
-- index.ts
-- items-dal.ts
-- items-bl.ts
output
-- index.js
-- index.d.ts
-- items-dal.d.ts
-- items-bl.d.ts
webpack.config
ts.config
package.json
So I copied the output folder to my other project but when I try to create a class that inherits one of my library's classes I get an error:
// user-dal.ts
const { ItemsDAL } = require("./output");
class UsersDAL extends ItemsDAL {
constructor() {
super("users");
}
}
export default new UsersDAL();
// usage
import usersDal from "./users-dal.ts";
usersDal.getAll() // <-- Property "getAll" doesn't exist on type usersDal
I know I can work around this by using require() but I'd prefer having the actual typings in my other projects.
The reason I'm doing this is because I'm obfuscating the index.js file but I don't mind exposing the name of the functions it contains. It may sound counter-productive but it provides enough security for my needs.
Is there a way to make typescript detect the d.ts files? (or any other way to have obfuscated code with typings)
Just add a "types" declaration to your package.json.
{
...
"main": "./output/index.js",
"types": "./output/index.d.ts",
...
}

How to use global variables in nest.js?

Taking configuration as an example, the Nest.js documentation advocates registering Config Modules and injecting them into other modules in a dependency injection way.
The benefits are obvious, and the dependencies and code are clear, but what if I have a nest.js project that needs to invoke the configuration information at startup? This actually caused me trouble.
My idea is to use a store (actually a closure) to manage all the variables that might be needed globally, the client-side link objects, registered at startup, and introduced when needed.
When corresponding variables are registered in this way, they can be introduced anywhere. The drawback is that you need to manage dependencies yourself.
With the above concept design of demo: https://github.com/sophons-space/nest-server.
Please e help me correct, I am still a rookie.
If you want to use Nest flow it should be defined in the configuration file
// app.module.ts
import configuration from './config/configuration';
imports: [
// first import as first initialization
ConfigModule.forRoot({
isGlobal: true, // to get access to it in every component
load: [configuration],
}),
]
...
// configuration.ts
export default (): any => {
return {
someGlobalConfigVariable: parseInt(process.env.PORT, 10) || 3000,
};
};
Create a file global.service.ts (inside a folder you can name it utils or whatever) & put the code bellow
export class GlobalService{
static globalVar: any;
}
Set value to the globalVar
GlobalService.globalVar = 'some value';
Get value from globalVar
console.log(GlobalService.globalVar);
N.B. Don't forget to import GlobalService wherever you want to use.
The way you can approach this is similar to how NestJS libraries or integrations usually handle configuration; using a method on the base module.
main.ts
import { NestFactory } from '#nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
// Note the `configure`-method
const app = await NestFactory.create(AppModule.configure({
myConfig: 'value',
});
await app.listen(3000);
}
bootstrap();
app.module.ts
import { DynamicModule } from '#nestjs/common';
export class AppModule {
static configure(config): DynamicModule {
return {
module: AppModule,
providers: [{ provide: 'CONFIG', useValue: config }],
// ....
}
}
}
You can use the general NodeJS approach
global.SomeGlobalVariableName = 'SomeGlobalVariableValue';
console.log(SomeGlobalVariableName);
Approach I used is using my config variables in yaml files and then getting those variables or objects wherever I want in my Nestjs project using config package. e.g in default.yml file
key: value
and then in file where I want to use this
import config from 'config';
let value = config.get<string>('key');
you can take this pkg from this npmjs link here
Why not go with more NestJs way i.e. with provide instance scope?
Most of the answers posted here are correct and easy to implement but I have a more generic way to define that variable that fits well in NestJs (Nestjs Scope and Dependency Injection flow). I would be happy to share the sample code, if required
Steps
Create a provider
Add a private instance variable to this provider - instead of a class variable (i.e. static variables) use an instance variable as NestJs automatically (by default) manages the instance of its providers in a singleton way i.e. a single instance of the provider is shared across the entire application. Read more about scopes here
Add get/set and other methods for that variable
Inject that provider wherever you need the global variable (instance variable of the provider(per instance).
Other ways of doing it
Config - preferrable for pre-defined types(like string, number...)
Static variable in util.ts file
Native Global variable - I would not recommend this(explanation is outside the scope of the question)

Why do I get "TypeError: Class extends value undefined is not a constructor or null" when trying to run webpack packaged node project

I have a simple application that is imported from webpack. The imported project exports a class like this...
export class BaseApp{...}
import { BaseApp } from "./MyClass"
import OtherThing from "./Other"
....
export { BaseApp, OtherThing }
Next I try to extend it in another node/express project...
import { BaseApp } from "#mine/util";
export class FullApp extends BaseApp{... }
I get...
class FullApp extends _mine_util__WEBPACK_IMPORTED_MODULE_0__["BaseA
pp"]{
^
TypeError: Class extends value undefined is not a constructor or null
How do I extend a class I am importing from Webpack?
Update here is an example project that demonstrates the issue...
https://github.com/jrgleason/simple-webpack-node
Impossible to say for certain when there is clearly much more to the source. If anyone else sees this error, the first thing to look for is circular dependencies: a file depends on some other file that (likely indirectly) depends on the first. JS must start somewhere, will not be able to ensure that a file is defined at the time that another file needs it, and will not try to go back and fill in the blanks later.
If you have more than a couple import / require statements, I recommend periodically running a checker like Madge to find and optionally visualize any loops before they become hard to undo.
npm i --saveDev madge
node node_modules/madge/bin/cli.js --warning --circular --extensions js ./

Resources