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

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.

Related

Cannot find module when using type from another module in class-validator

I'm using typescript on both frontend and backend, so I wanted to create a "shared types" package for them. For the backend I'm using nest.js and I recently ran into an issue with the class-validator package.
In my shared types package I created the following enum-like type (since enums itself don't seem to be working if they are being used from a node module):
export const MealTypes = {
BREAKFAST: 'Breakfast',
LUNCH: 'Lunch',
DINNER: 'Dinner',
SNACK: 'Snack'
} as const;
export type ObjectValues<T> = T[keyof T];
export type MealType = ObjectValues<typeof MealTypes>;
I've installed the module locally using npm i and I'm able to import the type in my backend like this:
import { MealType, MealTypes } from '#r3xc1/shared-types';
Since I am not able to use this constant for the IsEnum class validator, I wrote my own:
#ValidatorConstraint({ name: 'CheckEnum', async: false })
export class CheckEnumValidator implements ValidatorConstraintInterface {
validate(value: string | number, validationArguments: ValidationArguments) {
return Object.values(validationArguments.constraints[0]).includes(value);
}
defaultMessage(args: ValidationArguments) {
return `Must be of type XYZ`;
}
}
and then I'm using it in a DTO class like this:
export class CreateMealDTO {
#Validate(CheckEnumValidator, [MealTypes])
#IsNotEmpty()
meal_type: MealType;
}
But as soon as I add the #Validate(...) I get the following error on start:
Error: Cannot find module '#r3xc1/shared-types'
It only does this, if I am passing a type that has been imported from a node module into a validator. It also happens with other validators like IsEnum.
I'm not really sure why this error is happening and I appreciate any hints or help!

How to reference NodeJS.ProcessEnv in Nest.Js ConfigService

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

NestJS class-validators on incoming requests using interface

I need to use an interface through class-validator to validate the incoming form for a specific field in the incoming request body.
The interface:
export enum Fields {
Full_Stack_Dev = 'full stack dev',
Frontend_Dev = 'frontend dev',
Backend_Dev = 'backend dev',
}
export interface Experience {
field: Fields;
years: number;
}
And here is the DTO Class:
#IsEnum(Languages)
languages: Languages[];
experience: Experience[]; // 👈 Not sure which decorator to use for interfaces
Okay after doing a lot of research, I found a workaound for this:
First of all, Interfaces CANNOT be used directly. Officially declared by class-validators issue here
This is what I did:
Changed the interface into a separate class and added validation on its properties
class ExperienceDto {
#IsEnum(Fields)
field: Fields;
#IsNumber()
years: number;
}
Then used this class as type to validate Array of Objects in the ACTUAL DTO CLASS (not the above one)
#ArrayNotEmpty()
#ArrayMinSize(1)
#ArrayMaxSize(3)
#ValidateNested({ each: true })
#Type(() => ExperienceDto) // imported from class-transformer package
experience: ExperienceDto[];

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 express request object with sequelize model using typescript 2

I have some models, such as:
import * as Sequelize from 'sequelize'; // !
export interface UserAttributes {
id?: number;
email?: string;
googleToken?: string;
}
export interface UserInstance extends Sequelize.Instance<UserInstance, UserAttributes>, UserAttributes {
}
export interface UserModel extends Sequelize.Model<UserInstance, UserAttributes> {
}
// ... model definition
I need use import statement to use generic type Sequelize.Instance<TInstance, TAttributes>
I wright some middleware to add user instance to request object (or req.session object, doesn't metter)
Then I need typescript to know my new extending, for this I have file src/_extra.ts with content:
/// <reference path="./models/db.ts" /> -- also does not work
declare namespace Express {
export interface Request {
oauthClient: Googleapis.auth.OAuth2; // Googleapis is namespace
user?: UserInstance; // or ContactsProject.Models.UserInstance;
}
export interface Session {/* ... some same */}
}
I need to import my UserInstance type from my model file. Imports break all my _extra namespace definitions, inside namespace I can't use imports.
I have tried create namespace ContactsProject, but I getting same problem: if I use imports, typescript don't see my namespace, if I don't, I cant define my UserInstance type.
Sequelize doesn't declare namespaces.
I just want import some generic type in my namespace.
I use typescript Version 2.0.3.
When you use an import statement in a file, TypeScript treats it as a module. Therefor any namespace you declare inside it will not be global anymore, but local to that module.
Import your interface, and then wrap the namespace declaration in a declare global { ... }.

Resources