How to create common class for third-party API requests in NestJS - node.js

I am creating NestJS application where I am making third-party API requests. For that I have to write the same thing inside every function in order to get the data.
To make things non-repeating, how can I write on common class that has API request based on GET or POST request and send the response so that I can use that class in every function.
Below is my code:
subscribe.service.ts
#Injectable()
export class SubscribeService {
constructor(#InjectModel('Subscribe') private readonly model:Model<Subscribe>,
#Inject(CACHE_MANAGER) private cacheManager:Cache,
private httpService: HttpService){}
async addSubscriber(subscriberDto:SubscribeDto){
const url = 'https://track.cxipl.com/api/v2/phone-tracking/subscribe';
const headersRequest = {
'content-Type': 'application/json',
'authkey': process.env.AUTHKEY
};
try{
const resp = await this.httpService.post(url,subscriberDto,{ headers: headersRequest }).pipe(
map((response) => {
if(response.data.success == true){
const data = new this.model(subscriberDto);
// return data.save();
const saved = data.save();
if(saved){
const msgSuccess = {
"success":response.data.success,
"status":response.data.data.status
}
return msgSuccess;
}
}
else{
const msgFail = {"success":response.data.success}
return msgFail;
}
}),
);
return resp;
}
catch(err){
return err;
}
}
async getLocation(phoneNumber:PhoneNumber){
try{
const location = await this.cacheManager.get<Coordinates>(phoneNumber.phoneNumber);
if(location){
return location;
}
else{
const resp = await axios.post('https://track.cxipl.com/api/v2/phone-tracking/location',phoneNumber,{headers:{
'content-Type': 'application/json',
'authkey': process.env.AUTHKEY
}});
const msg:Coordinates = {
"location":resp.data.data.location,
"timestamp":resp.data.data.timestamp
}
await this.cacheManager.set<Coordinates>(phoneNumber.phoneNumber,msg, { ttl: 3600 });
return msg;
}
}
catch(err){
console.log(err);
return err;
}
}
}
As in above code in both function addSubscriber() and getLocation() I need to hit the API repeatedly and add request headers again and again is there any way so that I can create one separate class for request and response and utilize in my service.
How can I achieve desired the result?

To create a common class for making third-party API requests in NestJS, you can follow these steps:
Create a new file in your NestJS project to store the common class.
For example, you could create a file called api.service.ts in the
src/common directory.
In the file, create a new class called ApiService that will be responsible for making the API requests. This class should have a
constructor that injects the necessary dependencies, such as the
HttpService provided by NestJS.
import { HttpService, Injectable } from '#nestjs/common';
#Injectable()
export class ApiService {
constructor(private readonly httpService: HttpService) {}
}
Add methods to the ApiService class for each type of API request you want to make. For example, you might have a get() method for making GET requests, a post() method for making POST requests, and so on. Each method should accept the necessary parameters for making the request (such as the URL and any query parameters or request body), and use the HttpService to make the request.
import { HttpService, Injectable } from '#nestjs/common';
#Injectable()
export class ApiService {
constructor(private readonly httpService: HttpService) {}
async get(url: string, params?: object): Promise<any> {
return this.httpService.get(url, { params }).toPromise();
}
async post(url: string, body: object): Promise<any> {
return this.httpService.post(url, body).toPromise();
}
}
Inject the ApiService wherever you need to make API requests. For example, you might inject it into a service or a controller, and use the methods of the ApiService to make the actual API requests.
import { Injectable } from '#nestjs/common';
import { ApiService } from './api.service';
#Injectable()
export class SomeService {
constructor(private readonly apiService: ApiService) {}
async getData(): Promise<any> {
return this.apiService.get('https://some-api.com/endpoint');
}
}
This is just one way you could create a common class for making third-party API requests in NestJS. You can customize the ApiService class to meet the specific needs of your application

Related

NestJS GET Request not initialising the service

I have a service with a circular dependency.
When I send a get request that in turn calls a method in the service the class is not initialised. But when I send a delete or post request it works fine. None of these are making use of the circular dependency.
I'm not sure why it is failing here. How do I ensure the service is set up correctly.
controller
#Controller()
export class MainController {
constructor(
private readonly mainService: MainController,
) {}
#Get('/getsomething')
async getSomething() {
const response = await this.mainService.getSomething();
return response;
}
#Delete('/deletesomething')
async deleteSomething() {
const response = await this.mainService.deleteSomething();
return response;
}
}
#Injectable()
export class MainService {
constructor(
#Inject(forwardRef(() => CircularService))
private circularService: CircularService,
)
async getSomething () {
console.log ("this here is empty object ", this)
}
async deleteSomething () {
console.log ("this here is not empty object ", this)
}
}

How to excecute guard before injected provider into Scope.Request

I am working on a multi-tenant app using NestJS and I store the tenantId in the token using Jwt, I need to create a database tenant connection before I do database operations but the provider(code below) is being executed before the JwtAuthGuard but I need the guard to be executed first, Is there a way to change the order of execution?
Controller method (uses JwtAuthGuard):
#Post()
#UsePipes(new ValidationPipe())
#UseGuards(JwtAuthGuard)
create(#Body() createUserDto: CreateFruitDto) {
return this.fruitsService.create(createUserDto);
}
Passport strategy (JwtAuthGuard):
export class JwtStrategy extends PassportStrategy(Strategy) {
private logger = new Logger('JwtStrategy');
constructor(private configService: JwtConfigService) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: configService.ignoreExpiration,
secretOrKey: configService.options.secret,
});
}
async validate(payload: any) {
//injects user into req
return {
userId: payload.sub,
email: payload.email,
tenantId: payload.tenant,
};
}
}
Provider being injected into FruitsModule:
provide: 'TENANT_CONTEXT',
scope: Scope.REQUEST,
inject: [REQUEST],
useFactory: (req: Request): ITenantContext => {
const { user } = req as any;
Logger.log(user); // is undefined
const tenantContext: ITenantContext = {
user.tenantId,
};
return tenantContext;
},
IMHO best to avoid request scoped providers. That should have never been introduced in Nest. That scope bubbles up and makes everything above it request scoped as well.
You could introduce middleware to work around this. Middlewares are executed before guards. The auth guard validates and extracts data from the JWT token and stores it on req.user. Configure a middleware to prepare a user property on the request. Its setter will be executed when the auth guard sets the user property on the request and it will extract the tenant ID for you.
interface ExecutionMetadata {
tenantId?: number;
}
export class TenantContextMiddleware implements NestMiddleware {
public async use(req: Request, res: Response, next: NextFunction) {
this.metadata: ExecutionMetadata = { tenantId: req.user?.tenantId };
Object.definePropery(req, 'user', {
set(user) {
this._user = user;
this.metadata.tenantId = user?.tenantId;
},
get() {
return this._user;
}
});
next();
}
}
Here I extract the tenant ID from the req.user and store it on the req.metadata property.
Using the createParamdecorator() function from NestJS you could then write a simple parameter decorator to inject this metadata.
import { createParamDecorator, ExecutionContext } from '#nestjs/common';
export const Metadata = createParamDecorator(
(data: unknown, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest();
return request.metadata;
},
);
You can then use this decorator to inject this metadata into your controller.
#Controller('cats')
export class CatsController {
#Get()
findAll(#Metadata() metadata: ExecutionMedata): string {
...
}
}
Remark: This decorator will only work for controller methods! NestJS is able to resolve the value for you at that stage of the request. Similar to the #Body(), #Param(), #Query()...decorators. Then you can pass this metadata down as an argument. Or you could do something fancy and setup asynchronous context tracking.

NesJS : using an interceptor for HTTP and WS

I created an interceptor to edit data after passing the controller.
It works with HTTP but not with WS.
This is the code of my interceptor :
#Injectable()
export class SignFileInterceptor implements NestInterceptor {
constructor(private fileService: FilesService) {}
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
return next.handle().pipe(
map(async (data) => {
const paths = getFilesDtoPaths(data);
for (const path of paths) {
const file = get(data, path);
// Returns a promise
const signed = await this.fileService.signFile(file);
set(data, path, signed);
}
return data; // The data is edited and we can return it.
}),
);
}
}
To use it for HTTP, I add the interceptor to the app module :
providers: [
AppService,
{
provide: APP_INTERCEPTOR,
useClass: SignFileInterceptor,
}
]
With this, all my HTTP requests are intercepted, and the response is correct.
I want to make the same thing with WS using the same interceptor.
#WebSocketGateway({
cors,
allowEIO3: true,
})
#UseInterceptors(SignFileInterceptor) // Interceptor added HERE
#Injectable()
export class EventsGateway {
constructor() {}
#WebSocketServer()
server!: Server;
#SubscribeMessage('name1')
async handleJoinRoom(
): Promise<string> {
return 'john doe'
}
#SubscribeMessage('name2')
async handleJoinRoom(
): Promise<string> {
return 'john doe 2'
}
}
When a WS is triggered, the code is executed, but the data is returned BEFORE the end of my interceptor execution.
The data is not edited.
I appreciate your help.
Change map to mergeMap or switchMap to handle the async execution of the code. map from RxJS is a synchronous method.
This interceptor works well for HTTP and WS.
Another issue in my project caused the problem.
Sorry for the inconvenience.

How to accomplish HTTP Basic Authentication with NestJS

I am wondering how one can accomplish HTTP Basic Authentication with common NestJS Auth practices.
For example if I use an AuthGuard like this, I get the error
(node:336) UnhandledPromiseRejectionWarning: Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
import { CanActivate, ExecutionContext, Injectable } from '#nestjs/common';
import { InjectRepository } from '#nestjs/typeorm';
import { compareSync } from 'bcrypt';
import { User } from 'src/user/user.entity';
import { Repository } from 'typeorm';
#Injectable()
export class AuthGuard implements CanActivate {
constructor(
#InjectRepository(User) private readonly userRepository: Repository<User>,
) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const request = context.switchToHttp().getRequest();
const b64auth = (request.headers.authorization || '').split(' ')[1] || '';
const [username, password] = Buffer.from(b64auth, 'base64')
.toString()
.split(':');
const user = await this.userRepository.findOne({
where: { username },
});
if (user && compareSync(password, user.password) !== false) {
request.user = user;
return true;
}
const response = context.switchToHttp().getResponse();
response.set('WWW-Authenticate', 'Basic realm="Authentication required."'); // change this
response.status(401).send();
return false;
}
}
I suspect, that returning false (and letting Nest handle the reponse) doesn't play with "manually" setting the response status code to 401 and sending the response.
How can I protect certain routes with this ancient http authorization mechanism?
I would suggest implementing an ExceptionFilter that listens for Exception thrown by the guard specifically (UnauthorizedException). Then, in the filter, set the response as you would like to, that way the guard doesn't try to send multiple responses and you can set up the response as you'd like.

How to attach a class level filter with dependency injection?

I am trying to catch failures within a specific controller with filters. My filter requires access to another service (to save in db) and I am not sure how to have a class level filter with dependency injection (DI so that the filter has access to the service).
I've currently passing the service from the controller where I use the decorator UseFilters but realized that decorators don't share the same scope.
#UseFilters(new UnprocessableEntityExceptionFilter(myService))
#Catch(UnprocessableEntityException)
export class UnprocessableEntityExceptionFilter implements ExceptionFilter {
constructor(private readonly requestsService: RequestsService) { }
async catch(exception: UnprocessableEntityException, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse();
const request = ctx.getRequest();
const status = exception.getStatus();
const body = exception.message;
response.status(status).json(body);
await this.requestsService.create(request, response, body);
}
}
And I want to use this filter at the class level like this...
#UseFilters(new UnprocessableEntityExceptionFilter())
export class EventsController {
constructor() { }
#Get()
async get() {
...
}
But I clearly cannot create a new instance of UnprocessableEntityExceptionFilter because it requires dependency injection.
I understand the documentation tells us to use this method when filters have dependency injection, but I don't want this filter to be global.
#Module({
providers: [
{
provide: APP_FILTER,
useClass: HttpExceptionFilter,
},
],
})
I ended up using this
#UseFilters(UnprocessableEntityExceptionFilter)
export class EventsController {}

Resources