How to reference NodeJS.ProcessEnv in Nest.Js ConfigService - node.js

I have a HttpClientService class as shown below
#Injectable()
export class HttpClientService {
private readonly keyCloakClient: AxiosInstance;
constructor(protected readonly config: ConfigService) {
this.keyCloakClient = axios.create({
baseURL: config.get('KC_URL'),
timeout: 10_000,
headers: {
Accept: 'application/json',
},
});
}
}
I also have .env.development with this content
KC_URL=http://keycloack.local
KC_REALM=local
KC_CLIENT_ID=local-service
I'm using the library gen-env-types to generate type declaration from env variables.
After running the script my env.d.ts file has these content
declare global {
namespace NodeJS {
interface ProcessEnv {
KC_URL: string;
KC_REALM: string;
KC_CLIENT_ID: string;
}
}
}
export {};
Nest.js ConfigModule parses the env file properly and it works as expected. I bumped an issue when I tried to use more strict types there.
As you can see from the example I use config.get('KC_URL') to get a specific value. However, if I make a mistake and pass a different string that doesn't exist as an env variable it wouldn't complain during the development unless a runtime error happens.
I followed the official documentation where I could do things like this
interface Envs {
KC_URL: string;
}
#Injectable()
export class HttpClientService {
private readonly keyCloakClient: AxiosInstance;
constructor(protected readonly config: ConfigService<Envs>) {
this.keyCloakClient = axios.create({
baseURL: config.get('KC_URL', {infer: true}),
timeout: 10_000,
headers: {
Accept: 'application/json',
},
});
}
}
Although this example works just fine the problem lies here ConfigService<Envs>. Since I already have my env variable type declarations I'd like to reference them here instead of duplicating them.
I tried to reference it this way ConfigService<NodeJS.ProcessEnv> however couldn't get rid of the error. It works with the custom-defined type but not with NodeJS.ProcessEnv.
Any ideas or suggestions on how can I make it work without duplicating the types?
p.s.
This is a Typescript warning for that

Related

Proper way to manually instantiate Nest.js providers

I think I might be misunderstanding Nest.js's IoC container, or maybe DI as a whole.
I have a class, JSONDatabase, that I want to instantiate myself based on some config value (can either be JSON or SQL).
My DatabaseService provider:
constructor(common: CommonService, logger: LoggerService) {
// eslint-disable-next-line prettier/prettier
const databaseType: DatabaseType = common.serverConfig.dbType as DatabaseType;
if (databaseType === DatabaseType.JSON) {
this.loadDatabase<JSONDatabase>(new JSONDatabase());
} else if (databaseType === DatabaseType.SQL) {
this.loadDatabase<SQLDatabase>(new SQLDatabase());
} else {
logger.error('Unknown database type.');
}
}
My JSONDatabase class:
export class JSONDatabase implements IDatabase {
dbType = DatabaseType.JSON;
constructor(logger: LoggerService, io: IOService) {
logger.log(`Doing something...`)
}
}
However, the problem with this is that if I want my JSONDatabase to take advantage of injection, ie. it requires both IOService and LoggerService, I need to add the parameters from the DatabaseService constructor rather than inject them through Nest's IoC containers.
Expected 2 arguments, but got 0 [ts(2554)]
json.database.ts(7, 15): An argument for 'logger' was not provided.
Is this the proper way to do this? I feel like manually passing these references through is incorrect, and I should use Nest's custom providers, however, I don't really understand the Nest docs on this subject. I essentially want to be able to new JSONDatabase() without having to pass in references into the constructor and have the Nest.js IoC container inject the existing singletons already (runtime dependency injection?).
I might be completely off base with my thinking here, but I haven't used Nest all that much, so I'm mostly working off of instinct. Any help is appreciated.
The issue you have right now is because you are instantiating JSONDatabase manually when you call new JSONDatabase() not leveraging the DI provided by NestJS. Since the constructor expects 2 arguments (LoggerService, and IOService) and you are providing none, it fails with the message
Expected 2 arguments, but got 0 [ts(2554)]
I think depending on your use case you can try a couple of different options
If you fetch your configuration on startup and set the database once in the application lifetime you can use use a Custom provider with the useFactory syntax.
const providers = [
{
provide: DatabaseService,
useFactory: (logger: LoggerService, io: IOService, config: YourConfigService): IDatabase => {
if (config.databaseType === DatabaseType.JSON) {
return new JSONDatabase(logger, io);
} else if (databaseType === DatabaseType.SQL) {
return new SQLDatabase(logger, io);
} else {
logger.error('Unknown database type.');
}
},
inject: [LoggerService, IOService, YourConfigService]
},
];
#Module({
providers,
exports: providers
})
export class YourModule {}
If you have LoggerService, IOService and YourConfigurationService annotated with #Injectable() NestJS will inject them in the useFactory context. There you can check the databaseType and manually instantiate the correct IDatabase implementation. The drawback with this approach is that you can't easily change the database in runtime. (This might work just fine for your use case)
You can use strategy/factory pattern to get the correct implementation based on a type. Let say you have a method that saves to different databases based on an specific parameter.
#Injectable()
export class SomeService {
constructor(private readonly databaseFactory: DatabaseFactory){}
method(objectToSave: Object, type: DatabaseType) {
databaseFactory.getService(type).save(objectToSave);
}
}
#Injectable()
export class DatabaseFactory {
constructor(private readonly moduleRef: ModuleRef) {}
getService(type: DatabaseType): IDatabase {
this.moduleRef.get(`${type}Database`);
}
}
The idea of the code above is, based on the database type, get the correct singleton from NestJS scope. This way it's easy to add a new database if you want - just add a new type and it's implementation. (and your code can handle multiple databases at the same time!)
I also believe you can simply pass the already injected LoggerService and IOService to the DatabasesService you create manually (You would need to add IOService as a dependency of DatabaseServce
#Injectable()
export class DatabaseService {
constructor(common: CommonService, logger: LoggerService, ioService: IOService) {
// eslint-disable-next-line prettier/prettier
const databaseType: DatabaseType = common.serverConfig.dbType as DatabaseType;
if (databaseType === DatabaseType.JSON) {
this.loadDatabase<JSONDatabase>(new JSONDatabase(logger, ioService));
} else if (databaseType === DatabaseType.SQL) {
this.loadDatabase<SQLDatabase>(new SQLDatabase(logger, ioService));
} else {
logger.error('Unknown database type.');
}
}
}

How do you use a fallback exception filter in NestJs

I'm new to NestJs and I created a fallback exception filter, and now I would like to know how to use it. In other words, how do I import it in my application?
Here's my fallback exception filter:
#Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
public catch(exception: HttpException, host: ArgumentsHost): any {
/* Some code here */
return response.status(statusCode).json({
status: statusCode,
datetime: new Date(),
createdBy: "HttpExceptionFilter",
errorMessage: exception.message,
})
}
}
You'd need to bind the filter globally to be the fallback. You can do this one of two ways
With a custom provider in any module. Add this to the module's providers array
{
provide: APP_FILTER,
useClass: HttpExceptionFilter
}
This will still take effect in e2e tests, as it's part of the module definition
By using useGlobalFilters in your bootstrap method like so
app.useGlobalFilters(new HttpExceptionFilter());
This will not take effect in your e2e tests, so you'll need to bind it in those too, if you want the same functionality.
Just add this in your main.ts and it should work fine:
app.useGlobalFilters(new FallbackExceptionFilter();

Angular 8 adding component dynamically "viewContainerRef of undefined" error

I'm learning Angular and attempting to add a component to my HTML programmatically. However, I get the following error:
ERROR Error: Uncaught (in promise): TypeError: Cannot read property 'viewContainerRef' of undefined
The following are the key parts of code.
My Placeholder directive:
import { Directive, ViewContainerRef } from '#angular/core';
#Directive({
selector: '[appPlaceholder]'
})
export class PlaceholderDirective {
constructor(public viewContainerRef: ViewContainerRef) {}
}
Main.component.ts:
export class MainComponent implements OnInit {
#ViewChild(PlaceholderDirective, {static: false, read: ViewContainerRef}) alertHost: PlaceholderDirective;
constructor(private componentFactoryResolver: ComponentFactoryResolver) {}
ngOnInit(): void {
this.showMyComp();
}
private showMyComp() {
const testCmpFactory = this.componentFactoryResolver.resolveComponentFactory(TestComponent);
const hostViewContainerRef = this.alertHost.viewContainerRef;
hostViewContainerRef.clear();
hostViewContainerRef.createComponent(testCmpFactory);
}
}
Main.component.html:
<ng-template appPlaceholder></ng-template>
I have narrowed the issue down to const hostViewContainerRef = this.alertHost.viewContainerRef; in MainComponent.ts. The problem is that this.alertHost is null but I have been unable to figure out why.
I know this is an old question, but I had the same issue a few days ago:
Method that uses alertHost is invoked from ngOnInit().
To make sure that the references injected by #ViewChild are present, always write initialization code using ngAfterViewInit().
In addition to invoking showMyComp from ngAfterViewIinit, I had to update #ViewChild to following:
#ViewChild(PlaceholderDirective, {static: false}) alertHost: PlaceholderDirective;
(it is possible that this part is caused by different angular version)

How to properly design API module in TypeScript?

I want to design a TypeScript (2.7) module for accessing external IS, let's call it InfoSys. I used the following approach.
I created info-sys.ts which defines a API class and related interfaces and enums, like:
class Api {
constructor(private endpoint: Endpoint) {
// ...
}
}
enum Endpoint {
CONTACTS = "contacts"
}
interface Contact {
name: string;
}
Now I want to export all the stuff under specific name. So I appended the export statement:
export const InfoSys = {
Api,
Endpoint,
Contact
};
When I try to use the module in another file, like:
import { InfoSys } from "info-sys";
// this line throws error: "Cannot find namespace 'InfoSys'"
private api: InfoSys.Api;
// but this line is ok
api = new InfoSys.Api(InfoSys.Endpoint.CONTACTS);
The way that works is the following - to export every piece individually:
export class Api {
constructor(private endpoint: Endpoint) {
// ...
}
}
export enum Endpoint {
CONTACTS = "contacts"
}
export interface Contact {
name: string;
}
and import them all to a single variable:
import * as InfoSys from "info-sys";
But the name of the variable can be whatever. It is not critical for functionality but I want to force developers, who will use the info-sys module, to use a specific name while accessing it (for easier readability and maintainability). How to properly design such module?
You can use namespace:
export namespace InfoSys {
Api,
Endpoint,
Contact
};
In general, this approach should be avoided. But in your case, it is fine as you are delivering things that are tightly related.
If Api is the single entry point to all these, I would also recommend this:
export class InfoSysApi { ... }
export namespace InfoSysApi {
export enum Endpoint = { ... }
export interface Contact { ... }
}
UPDATE:
To make sure I get the point through, DON'T do the following:
export namespace Foo {
export function X() { return 'x' }
export function Y() { return 'y' }
}
Only use export namespace to export "tugged in types", not values.
In TypeScript handbook: https://www.typescriptlang.org/docs/handbook/declaration-merging.html
Although the table says namespace can contain values, it is considered bad practice if you are writing ESM (import/export).
Namespace and ESM are two different mechanisms to achieve similar result.
Don't mix them together.

Extend Request interface of Express for old JS library that doesn't support typings yet

I need node-vbauth in a TypeScript project. Sadly, there are no types yet. So when I want to access req.vbuser I got an error that his property doesn't exist. To fix this, I want to provide the missing type in a custom.d.tsfile:
export namespace Express {
export interface Request {
vbuser?: VbSessionInfo
}
}
export interface VbSessionInfo {
userid: number;
username: string;
usergroupid:number;
membergroupids:Array<string>;
email:string;
posts:number;
subscriptionexpirydate: number;
subscriptionstatus:number;
}
But this doesn't work. Typescript stil says the property vbuseris missing. I tried to explicitly include it in my tsconfig.json, no difference.
Found out that I'm missing to set my declaration in the global namespace and import the original Type which is going to be extended:
import {Request} from 'express';
declare global {
export namespace Express {
export interface Request {
vbuser: VbSessionInfo
}
}
export interface VbSessionInfo {
userid: number;
username: string;
usergroupid:number;
membergroupids:Array<string>;
email:string;
posts:number;
subscriptionexpirydate: number;
subscriptionstatus:number;
}
}
This works perfect, including intellisense in VS Code.

Resources