Nestjs HttpService error handling with AxiosRequestConfig's validateStatus function - node.js

I need to handle http errors status code (such as 401, 500, etc) which can occur when consuming an external service using HttpService (HttpModule of Nestjs). Here is the implementation i am working on:
import { Injectable } from '#nestjs/common';
import { HttpService } from '#nestjs/axios';
import { Logger } from '#nestjs/common';
import { AxiosRequestConfig } from 'axios';
import { catchError, firstValueFrom, map } from 'rxjs';
type Person = {
name: string;
lastName: string;
};
#Injectable()
export class PersonService {
constructor(private httpService: HttpService) {}
async findPerson(): Promise<Person> {
const axiosConfig: AxiosRequestConfig = {
method: 'get',
url: 'https://service.dns/path/person',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${jwt}`,
},
validateStatus: function (status: number) {
return status === 200;
},
};
const personInstance: Person = await firstValueFrom(
this.httpService.request(axiosConfig).pipe(
catchError((e) => {
Logger.error(e.response.data.errorMessage);
throw new Error('internal communication error');
}),
map((res) => {
return res.data;
}),
),
);
return personInstance;
}
}
In the code above, I just need the function catchError throws the custom error, but I am not able to make the function validateStatus to trigger the execution of catchError.

I have implemented next code in order to take advantage of validateStatus function of AxiosRequestConfig for giving solution to my needs:
import { Injectable } from '#nestjs/common';
import { HttpService } from '#nestjs/axios';
import { Logger } from '#nestjs/common';
import { AxiosRequestConfig } from 'axios';
import { firstValueFrom } from 'rxjs';
type Person = {
name: string;
lastName: string;
};
#Injectable()
export class PersonService {
constructor(private httpService: HttpService) {}
async findPerson(): Promise<Person> {
const axiosConfig: AxiosRequestConfig = {
method: 'get',
url: 'https://service.dns/path/person',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer fake_jwt`,
},
validateStatus: function (status: number) {
return status === 200;
},
};
return firstValueFrom(this.httpService.request(axiosConfig))
.then((res) => res.data)
.catch((e) => {
Logger.error(e.errorMessage);
throw new Error('internal communication error');
});
}
}
Note: this code deals with Promise<AxiosResponse<any>> instead of Observable<AxiosResponse<any> methods

Related

How to use external (from another package) exception filter Nest.js?

I'm trying to create shared-module for microservices.
There are two packages:
#name/hub - ordinary HTTP-service
#name/lib - shared library
Library contains simple exception-filter module:
http.exception-filter.ts
import { ArgumentsHost, Catch, ExceptionFilter, HttpException } from '#nestjs/common';
import { Request, Response } from 'express';
#Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
catch(exception: HttpException, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
const request = ctx.getRequest<Request>();
const status = exception.getStatus();
response.status(status).json({
statusCode: status,
timestamp: new Date().toISOString(),
path: request.url,
message: exception.message,
});
}
}
exceptions-fitlers.module.ts
import { Module, Scope } from '#nestjs/common';
import { HttpExceptionFilter } from './http.exception-filter';
import { APP_FILTER } from '#nestjs/core';
#Module({
providers: [
{
provide: APP_FILTER,
scope: Scope.REQUEST,
useClass: HttpExceptionFilter,
},
],
})
export class ExceptionsFiltersModule {}
Service contains controller that uses this filter:
app.module.ts
import { Module } from '#nestjs/common';
import { ExceptionsFiltersModule } from '#name/nodejs-lib/dist';
#Module({
imports: [ExceptionsFiltersModule, ...],
})
export class AppModule {}
controller.ts
#Controller('app')
#UseFilters(new HttpExceptionFilter())
export class AppController{
#Post('/check')
#HttpCode(200)
async check(#Body() dto: A): Promise<B> {
throw new BadRequestException('Invalid data');
}
}
main.ts
import { NestFactory } from '#nestjs/core';
import { AppModule } from './modules/app.module';
import { ConfigService } from '#nestjs/config';
import { DocumentationBuilder, HttpExceptionFilter } from '#name/nodejs-lib/dist';
async function bootstrap() {
const app = await NestFactory.create(AppModule, { cors: true });
const config = app.get(ConfigService);
app.useGlobalFilters(new HttpExceptionFilter());
await app.listen(config.get<number>('HTTP_PORT'), () => {
logger.log(`HTTP Server: http://${config.get('HTTP_HOST')}:${config.get('HTTP_PORT')}`);
});
}
bootstrap().then();
Then I trying trigger this filter, I receive generic response:
{
"statusCode": 400,
"message": "Invalid data",
"error": "Bad Request"
}
If someone has opinion, please let me know. Thanks

What could be causing localhost:4200 to be added in front of route call?

I'm trying to make a call to an API, but for some reason localhost:4200 is being added in front of my route call to localhost:3000.
I've looked through my code several times and have another API call that is functioning fine with the same type of code that I am using to do this API call.
Here is my service:
import { Injectable } from '#angular/core';
import { HttpClient, HttpHeaders } from '#angular/common/http';
import { environment } from '../../environments/environment';
interface Tweet {
name: string;
}
#Injectable({
providedIn: 'root'
})
export class TweetService {
private url = environment.tweetsAPI;
httpOptions = {
headers: new HttpHeaders({'Content-Type': 'application/json'})
};
constructor(private http: HttpClient) {}
getTweets() {
console.log(this.url);
return this.http.get(this.url);
}
}
Here is my route:
app.route('/get-tweets').get((req, res) => {
T.get('search/tweets', { q: 'iowa state since:2018-07-11 #gameday -filter:media -filter:retweets', count: 10 }, function(err, data, response) {
let filteredTweets = [];
_.each(data, userObj => {
filteredTweets.push(userObj);
})
return res.status(200).json({
message: 'Successfully returned tweets',
tweets: filteredTweets
})
})
})
And inside my environment.ts file:
tweetsAPI: 'http:/localhost:3000/get-tweets'
Getting this error:
ERROR HttpErrorResponse {headers: HttpHeaders, status: 404, statusText: "Not Found", url: "http://localhost:4200/localhost:3000/get-tweets", ok: false, …}
Replace tweetsAPI: 'http:/localhost:3000/get-tweets'
with
tweetsAPI: 'http://localhost:3000/get-tweets'
duplicate
You need to add your protocol for your URL. Otherwise, it's a relative URL:
.post('http:/localhost:3000/get-tweets', { headers: this.headers })

API in mean stack

Is it possible to do the following with service in angular6?
How to call api for the following requirement?
1. Mandatory properties check
2. return response code 400 with right message if there is any mandatory check failures
3. data type validations
4. return response code 400 with right message
5. Initialize DB call
6.run DB queries and return response
7. If any errors during the DB call, return reponse code 500 with right message
8.if success then return the response object.
This is something new to me , hence don't know how to implement it. So couldn't try anything .
//blog.service.ts
import { Injectable, Inject, Output, EventEmitter } from '#angular/core';
import { HttpClient, HttpHeaders } from '#angular/common/http';
import { Observable } from 'rxjs';
import { APP_CONFIG, AppConfig } from '../app.config';
import { map } from 'rxjs/operators';
#Injectable({
providedIn: 'root'
})
export class BlogService {
#Output() userLoggedIn: EventEmitter<any> = new EventEmitter();
constructor(private http: HttpClient, #Inject(APP_CONFIG) private _config: AppConfig) { }
////////////////////////////////////////////////////
Save(blogForm: any): Observable<any> {
return Observable.create(observer => {
this.http.post(this._config.apiEndpoint + '/newblog',
JSON.stringify(blogForm), { headers: new HttpHeaders({ 'Content-Type': 'application/json' }) }
)
.subscribe((response: Response) => {
observer.next(response);
observer.complete();
});
});
}
////////////////////////////////////////////////////
allBlogs(): any{
return this.http.get(this._config.apiEndpoint + '/getblogs').pipe(map(response=>response));
}
////////////////////////////////////////////////////
recentBlogs(): any{
return this.http.get(this._config.apiEndpoint + '/get-recent-blogs').pipe(map(response=>response));
}
////////////////////////////////////////////////////
viewBlog(blog_id:number): any{
return this.http.get(this._config.apiEndpoint+'/viewblog/'+blog_id).pipe(map(response=>response));
}
////////////////////////////////////////////////////
datewiseBlogs(year:any,month:any): any{
return this.http.get(this._config.apiEndpoint+'/year-blogs/'+year+'/month/'+month).pipe(map(response=>response));
}
////////////////////////////////////////////////////
deleteblog(blogid): Observable<any> {
return Observable.create(observer => {
this.http.post(this._config.apiEndpoint+'/deleteblog', { _id: blogid }, { headers: new HttpHeaders({ 'Content-Type': 'application/json' }) }
).subscribe((response: Response) => {
observer.next(response);
observer.complete();
});
});
}
////////////////////////////////////////////////////
editblog(blogForm: any): Observable<any> {
return Observable.create(observer => {
this.http.post(this._config.apiEndpoint + '/updateblog',
JSON.stringify(blogForm), { headers: new HttpHeaders({ 'Content-Type': 'application/json' }) }
).subscribe((response: Response) => {
observer.next(response);
observer.complete();
});
});
}
////////////////////////////////////////////////////
}
Expected result is to call the api in the service above and do other operations as listed above.

call a web service , by an action button with angular , from nodeJS server ,

want to call a rest API from a nodeJS server with a click of a button in my project angular to add a user in the database, the api in the server is connected to my mysql database , just i wonna invoke the api rest of add ou update ou delete from the button in my project angular . I am new to this I dont know how to proceed.
thank you
import { Component } from '#angular/core';
import { LocalDataSource } from 'ng2-smart-table';
import { Injectable } from '#angular/core';
import { HttpClient } from '#angular/common/http';
import { SmartTableData } from '../../../#core/data/smart-table';
//import { EmailValidator } from '#angular/forms';
#Injectable()
export class ConfigService {
constructor(private http: HttpClient) { }
}
#Component({
selector: 'ngx-smart-table',
templateUrl: './smart-table.component.html',
styles: [`
nb-card {
transform: translate3d(0, 0, 0);
}
`],
})
#Injectable()
export class Configuration {
public server = 'http://localhost:3000/';
public apiUrl = 'api/';
public serverWithApiUrl = this.server + this.apiUrl;
}
export class SmartTableComponent {
settings = {
add: {
addButtonContent: '<i class="nb-plus"></i>',
createButtonContent: '<i class="nb-checkmark"></i>',
cancelButtonContent: '<i class="nb-close"></i>',
actionButonContent:'<i (click)="makeServiceCall($event)"><i/>',
},
edit: {
editButtonContent: '<i class="nb-edit"></i>',
saveButtonContent: '<i class="nb-checkmark"></i>',
cancelButtonContent: '<i class="nb-close"></i>',
actionButonContent:'<i (click)="onEditConfirm($event)"></i>'
},
delete: {
deleteButtonContent: '<i class="nb-trash"></i>',
confirmDelete: true,
actionButonContent:'<i (click)="onDeleteConfirm($event)"></i>'
},
columns: {
id: {
title: 'ID',
type: 'number',
},
firstName: {
title: ' Name',
type: 'string',
},
email: {
title: 'E-mail',
type: 'string',
},
password: {
title: 'password',
type: 'password',
},
},
};
source: LocalDataSource = new LocalDataSource();
constructor(private service: SmartTableData) {
const data = this.service.getData();
this.source.load(data);
}
onDeleteConfirm(event): void {
if (window.confirm('Are you sure you want to delete?')) {
event.confirm.resolve();
} else {
event.confirm.reject();
}
}
}
and this is my app.js (server)
var express = require('express');
var router = express.Router();
var user=require('../model/user');
router.get('/:id?',function(req,res,next){
if(req.params.id){
user.getUserById(req.params.id,function(err,rows){
if(err)
{
res.json(err);
}
else{
res.json(rows);
}
});
}
else{
user.getAllUsers(function(err,rows){
if(err)
{
res.json(err);
}
else
{
res.json(rows);
}
});
}
});
router.post('/',function(req,res,next){
user.addUser(req.body,function(err,count){
if(err)
{
res.json(err);
}
else{
res.json(req.body);
}
});
});
router.delete('/:id',function(req,res,next){
user.deleteUser(req.params.id,function(err,count){
if(err)
{
res.json(err);
}
else
{
res.json(count);
}
});
});
router.put('/:id',function(req,res,next){
user.updateUser(req.params.id,req.body,function(err,rows){
if(err)
{
res.json(err);
}
else
{
res.json(rows);
}
});
});
module.exports=router;
To make a call you need to add the HttpClientModule in your app.module.ts as import.
Then inject the Http client it wherever you want to use it:
constructor(private http: HttpClient){}
to use it just do:
this.http.get(<<url>>) //for get request
this.http.post(<<url>>,obj) //for post request
this returns an observable from which you can map the results and catch errors using Rxjs operators. for eg
addUser(user){ //called on button click
this.http.post(yourUrl,Json.Stringify(user)).pipe(
map((res)=>{
//do something with response
return 'sucess'
}),
catchError(err => {
//handleError
}
).subscribe(); // dont forget to subscribe
}
if you want to learn more : https://angular.io/guide/http
and for rxjs: https://www.learnrxjs.io/
Assume that the data that needs to be sent to the server is being passed to the function as the "data" parameter. Add "HttpClientModule" to the main app module or to your custom module if any as follows. Custom service has been imported in the app module or import it in your module accordingly.
app.module.ts
import { NgModule } from '#angular/core';
import { CommonModule } from '#angular/common';
import { FormsModule } from '#angular/forms';
import { HttpClientModule } from '#angular/common/http';
import { CustomService } from 'location-of-custom-service';
#NgModule({
imports: [
CommonModule,
FormsModule,
HttpClientModule
],
declarations: [],
providers: [CustomService]
})
export class AppModule { }
Create a service file as follows.
custom.service.ts
import { Injectable } from '#angular/core';
import { Router } from '#angular/router';
import { HttpClient, HttpHeaders } from '#angular/common/http';
import { Observable, throwError } from 'rxjs';
import { retry, catchError } from 'rxjs/operators';
const httpOptions = {
headers: new HttpHeaders({ 'Content-Type': 'application/json' })
};
#Injectable({
providedIn: 'root'
})
export class CustomService {
public server = 'http://localhost:3000/';
public apiUrl = 'api/';
public serverWithApiUrl = this.server + this.apiUrl;
private fetchDataURL = this.serverWithApiUrl + 'fetchSomeData';
private addDataURL = this.serverWithApiUrl + 'addSomeData'
constructor(private _http: HttpClient) { }
// Fetch data
fetchData(id): Observable<any> {
this.fetchDataURL = this.fetchDataURL + "/" + id;
return this._http.get<any>(this.fetchDataURL, httpOptions)
.pipe(
retry(1),
catchError(this.handleError)
);
}
// Add data
addData(data): Observable<any> {
return this._http.post<any>(this.addDataURL, data, httpOptions);
}
// Error handler - you can customize this accordingly
handleError(error) {
let errorMessage = '';
if (error.error instanceof ErrorEvent) {
// client-side error
errorMessage = `Error: ${error.error.message}`;
} else {
// server-side error
errorMessage = `Error Code: ${error.status}\nMessage: ${error.message}`;
}
return throwError(errorMessage);
}
}
Your existing component has been modified so as to have the new additions as follows.
smarttable.component.ts
import { Component, OnInit } from '#angular/core';
import { Router, ActivatedRoute } from '#angular/router';
import { FormControl, FormGroup, Validators } from '#angular/forms';
import { CustomService } from './custom-service-location';
#Component({
selector: 'ngx-smart-table',
templateUrl: './smart-table.component.html'
})
export class SmartTableComponent implements OnInit {
constructor(private customService: CustomService) {}
fechedData: any;
// you existing codes goes here
// Add data - assume the data that needs to be sent to the server is as "data"
makeServiceCall(data) {
this.customService.addData(data)
.subscribe((data) => {
console.log(data);
// your logic after data addition goes here
},
(error) => {
// logic to handle error accordingly
});
}
// Fetch data
getData(id) {
this.customService.fetchData(id)
.subscribe((data) => {
console.log(data);
this.fechedData = data;
// your logic after data fetch goes here
},
(error) => {
// logic to handle error accordingly
});
}
}
I hope the above helps.

GET Request 401 (Unauthorized)

In Postman the profile is authorized and a json object is returned.
But on the front-end, I'm getting this error.
HttpErrorResponse {headers: HttpHeaders, status: 401, statusText: "Unauthorized", url: "http://localhost:3000/users/profile", ok: false, …}
Here is my auth.service.ts file:
import { Injectable } from '#angular/core';
import { HttpClient, HttpHeaders } from '#angular/common/http';
import { Observable, Subject } from 'rxjs';
import 'rxjs/add/operator/map';
interface data{
success: boolean;
msg: string;
token: string;
user: any;
}
export class AuthService {
authToken: any;
user: any;
constructor(private http: HttpClient) { }
getProfile() {
let headers = new HttpHeaders();
this.loadToken();
headers.append('Authorization', this.authToken);
headers.append('Content-Type', 'application/json');
return this.http.get<data>('http://localhost:3000/users/profile', {headers: headers})
.map(res => res);
}
loadToken(){
const Token = localStorage.getItem('id_token');
this.authToken = Token;
}
}
profile.ts file:
import { Component, OnInit } from '#angular/core';
import { AuthService } from '../../services/auth.service';
import { Router } from '#angular/router';
#Component({
selector: 'app-profile',
templateUrl: './profile.component.html',
styleUrls: ['./profile.component.css']
})
export class ProfileComponent implements OnInit {
user: Object;
constructor(
private authService: AuthService,
private router: Router,
) { }
ngOnInit() {
this.authService.getProfile().subscribe(profile => {
this.user = profile.user;
},
err => {
console.log(err);
return false;
});
}
}
this is happening because this.loadToken() returns you the token. you have to wait till you this.loadToken() function returns you the authToken.
for this, you can use async and await for your function or else just return the value from this.loadToken like
getProfile() {
let headers = new HttpHeaders();
this.authToken = this.loadToken();
headers.append('Authorization', this.authToken);
headers.append('Content-Type', 'application/json');
return this.http.get<data>('http://localhost:3000/users/profile', {headers: headers})
.map(res => res);
}
loadToken(){
const Token = localStorage.getItem('id_token');
return Token;
}
Not sure this will help but try to enable CORS in your WebAPI project:
WebApiConfig.cs (Register Method):
EnableCorsAttribute cors = new EnableCorsAttribute("*", "*", "*");
config.EnableCors(cors);

Resources