I'm facing a problem that i'd like to fix...
I'm working on a procject using Nest.js as Backend framework for APIs, and Nuxt.js for Client...
Everything is working fine, but when i'm trying to throw an error in a service, that is injected into the controller, i'm not able to send a custom response to the client. Those the scenarios that i've faced:
account.service.ts
async createAccount(_account: AccountEntity){
return await this.accountRepository.save(_account)
}
async _accountExists(_account: AccountEntity) {
const itExists = await this.findOne(_account)
if(itExists){
throw new ConflictException(`Username already exists!`)
}
}
account.controller.ts
#Post()
#UseFilters(new HttpExceptionFilter())
async createAccount(#Body() userAccount: createAccountDto, #Res() res: Response) {
try {
await this.accountService._accountExists(userAccount).then(async () => {
return await this.accountService.createAccount(userAccount)
})
} catch (e) {
res.status(e.status).json(e.message)
}
}
This returns me this error in the client, if the user already exists but it doesn't send the json to the client.
POST http://localhost:3000/account 409 (Conflict)
Request failed with status code 409
If i change it res.json(e), it sends me to the client the error with status 201 as you can see in the image, but the response is fine in all scenarios.
So the question is... how i can get this response with correct status code?
This is the Exception Filter:
import { ExceptionFilter, Catch, ArgumentsHost, 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,
name: exception.name,
message: exception.message.message,
timestamp: new Date().toISOString(),
path: request.url,
});
}
}
Seems that the problem itself wasn't the server, but from client.
Loggin the errors doesnt log the object, you need to log errors.response
Related
So this is driving me nuts because it was working at one point, and I cannot find anything I changed that would make it not work.
Here is my simple axios post request. Based on my debugging, this is successfully making the network request and the server is returning a 422 error, which I expect (testing invalid login credentials). However, when attempting to log the axios reponse to the console, I get "undefined". It is also returning a resolved promise.
const login = (loginParams) => {
return axiosLoggedOut.post("/login", loginParams);
};
Here is the code for the axios instance I am using. I am only intercepting the request in this, and I know the request it making it to the server, so I don't think the issue would be in here:
import Cookies from "js-cookie";
import axios from 'axios';
const axiosLoggedOut = axios.create({
baseURL: "http://localhost",
withCredentials: true,
});
const requestTypes = ['post', 'put', 'delete'];
const onRequest = (config) => {
if (requestTypes.includes(config.method) && !Cookies.get('XSRF-TOKEN')) {
return setCSRFToken().then(() => config);
}
return config;
}
const setCSRFToken = () => {
return axiosLoggedOut.get('/sanctum/csrf-cookie');
}
axiosLoggedOut.interceptors.request.use(onRequest, null);
export default axiosLoggedOut;
Am I missing something obvious here?
this is the all-exception.filter.ts:
#Catch()
export class AllExceptionsFilter implements ExceptionFilter {
constructor(private readonly httpAdapterHost: HttpAdapterHost) {}
catch(exception: HttpException, host: ArgumentsHost) {
const { httpAdapter } = this.httpAdapterHost;
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
const request = ctx.getRequest<Request>();
const message = exception.message;
const status =
exception instanceof HttpException
? exception.getStatus()
: HttpStatus.INTERNAL_SERVER_ERROR;
console.log();
const responseBody = {
success: false,
message,
};
httpAdapter.reply(ctx.getResponse(), responseBody, status);
}
}
and this is a service method that returning just one item:
findOne(id: number) {
return this.prisma.restaurant.findUniqueOrThrow({
where: {
id,
},
});
}
The problem is that findUniqueOrThrow will throw 404 if the item is not found. but in the global filter when I log the status, I always receive a 500 status code.
Here a full example for filter handling when Prisma.findFirstOrThrow throw not found exception:
notFound.filter.ts
import { ArgumentsHost, Catch, ExceptionFilter } from '#nestjs/common';
import { NotFoundError } from '#prisma/client/runtime';
import { Response } from 'express';
#Catch(NotFoundError)
export class NotFoundExceptionFilter implements ExceptionFilter {
public catch(exception: NotFoundError, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
return response.status(404).json({ statusCode: 404, error: 'Not Found' });
}
}
main.ts
async function bootstrap() {
const app = await NestFactory.create(AppModule);
...
app.useGlobalFilters(new NotFoundExceptionFilter());
await app.listen(3000);
}
Et voila !
prisma on it's own does not throw an HttpException from Nest, which is where the getStatus() method exists. The error thrown will also fail the exception instanceof HttpException check. You should wrap the call in a try/catch and transform the error to the appropriate exception type so that Nest's filter can handle sending back to proper exception status code
In the Axios document:
axios.get('/user/12345')
.catch(function (error) {
if (error.response) {
// The request was made and the server responded with a status code
// that falls out of the range of 2xx
console.log(error.response.data);
console.log(error.response.status);
console.log(error.response.headers);
} else if (error.request) {
// The request was made but no response was received
// `error.request` is an instance of XMLHttpRequest in the browser and an instance of
// http.ClientRequest in node.js
console.log(error.request);
} else {
// Something happened in setting up the request that triggered an Error
console.log('Error', error.message);
}
console.log(error.config);
});
we know we can catch the error in the .catch() method.
But when I use the Django-Rest-Framework as the backend API provider. it only provide the data, there is no status in it:
You see the error:
{username: ["A user with that username already exists."]}
but in the browser, we can know the status code:
Before asking this question, I have read How can I get the status code from an http error in Axios?
this post.
But the post seems different with mine.
EDIT-1
In my Django-Rest-Framework project:
the view:
class UserCreateAPIView(CreateAPIView):
serializer_class = UserCreateSerializer
permission_classes = [AllowAny]
queryset = User.objects.all()
the serializer:
class UserCreateSerializer(ModelSerializer):
"""
user register
"""
class Meta:
model = User
fields = [
'username',
'wechat_num',
'password',
]
extra_kwargs = {
"password":{"write_only":True}
}
def create(self, validated_data):
username=validated_data.pop('username')
wechat_num = validated_data.pop('wechat_num')
password=validated_data.pop('password')
user_obj = User(
username=username,
wechat_num=wechat_num,
)
user_obj.set_password(password)
user_obj.save()
group=getOrCreateGroupByName(USER_GROUP_CHOICES.User)
user_obj.groups.add(group)
return validated_data
I find in the interceptors configuration:
Axios.interceptors.response.use(
res => {
return res;
},
error => {
return Promise.reject(error.response.data)
}
);
I was return the error.response.data directly, I could configure it to error.response, or error.
if I configure the error.response, then in the .catch() I can console like bellow:
console.log(response.data);
console.log(response.status);
console.log(response.headers);
I am running app on localhost://3000 with npm server
Services file:
import {Injectable} from "#angular/core";
import {Jsonp} from "#angular/http";
import 'rxjs/add/operator/map';
#Injectable()
export class futScoreService{
constructor(private _jsonp:Jsonp){}
getCompetitions(){
let queryString ='?callback=JSONP_CALLBACK';
return this._jsonp.get('http://api.football-data.org/v1/competitions/' + queryString,{method: 'Get'})
.map((res) => res.json());
}
}
Component file:
ngOnInit(){
this._futScoreService.getCompetitions().subscribe(
(comp)=>{
console.log(comp);
},
(err)=>{
console.log(err);
}
);
}
And I'm getting this error in console console-error
and on network tab I get object from API network-tab
Ok solution was making get request with http module and providing header with get request. Header part was main reason why it was failing.
let headers = new Headers({'X-Mashape-Key':'Ns0SkjyRRomshq3PgEnGoz2Zkc71p1CYnWajsnphGctvrGt46W'});
headers.append( 'Accept', 'application/json');
return this._http.get("http://api.football-data.org/v1/competitions/",{
headers: headers
})
.map((res) => res.json());
Angular is replacing JSONP_CALLBACK with
__ng_jsonp____req0_finished
but it should be
__ng_jsonp__.__req0.finished
Inspect your Network response. If you see __ng_jsonp____req0_finished({...json object...}) this is the problem.
Also, some services have different requirements for the callback query string parameter, which proves to be nasty because the error is exactly the same. I was using &callback=__ng_jsonp__.__req0.finished with MailChimp which produced the same error but the response had only a json object and no callback function. This is because MailChimp's spec is to use &c= instead of &callback=
When hardcoding the Jsonp callback (re: JSONP_CALLBACK issue) you need to account for the number of calls made, as Angular persists the state of each call. An example of what I'm doing for Mailchimp:
addEmailToList(email: string, listId: string, jsonpCalls: number, callback: any) {
const cbJsonp = '__ng_jsonp__.__req' + jsonpCalls + '.finished';
let url = [
'http://',
host,
'/subscribe',
'/post-json',
].join('');
let queryParams: URLSearchParams = new URLSearchParams();
queryParams.set('u', Config.MAILCHIMP_API_KEY);
queryParams.set('id', listId);
queryParams.set('EMAIL', email);
queryParams.set('c', cbJsonp); // non-standard; varies by service; usually 'callback'
...
}
this._InstUrl = "your url";
let params1 = new URLSearchParams();
//params.set('search', term); // the user's search value
//params.set('action', 'opensearch');
params1.set('format', 'json');
//params1.set('callback', "ng_jsonp.__req0.finished");
params1.set('callback', "JSONP_CALLBACK");
return this._jsonp
.get(this._InstUrl, { search: params1 })
.map(response => { debugger; this.Result = response.json().data })
.subscribe(
(data) => {
debugger
console.log(this.Result);
},
(error) => {
debugger
console.log(error);
});
I'm trying to use an Angular 2 HTTP GET request to simply connect with a Node/Express backend that responds with a list of the file names in a certain folder using the fs.readdir method.
I set up the Angular 2 request as a service:
import { Injectable } from '#angular/core';
import { Http, Response } from '#angular/http';
import { Observable } from 'rxjs/Observable';
import './rxjs-operators';
#Injectable()
export class PhotoService {
constructor (private http: Http) {}
private photosUrl = '/api/photos'; // URL to web API
getPhotos() : Observable<string[]> {
return this.http.get(this.photosUrl)
.map(this.extractData)
.catch(this.handleError);
}
private extractData(res: Response) {
let body = res.json();
return body.data || { };
}
private handleError (error: any) {
let errMsg = (error.message) ? error.message :
error.status ? `${error.status} - ${error.statusText}` : 'Server error';
console.error(errMsg); // log to console instead
return Observable.throw(errMsg);
}
}
and then called this service from a component:
ngOnInit() {
this.photoService.getPhotos()
.subscribe(
photos => this.fileList = photos,
error => this.errorMessage = <any>error);
}
This is the Node backend (with Express set up as per conventions):
//Photo Service
app.get('/api/photos', function(req, res) {
fs.readdir('./uploads', function(error, files) {
if (error) {
throw error;
}
else {
res.end(files);
}
});
});
As seen, the HTTP request calls a GET method to http://localhost:3000/api/photos and the Node backend is supposed to receive that request and send back an array of strings that have the names of files in the 'uploads' folder.
However it does not seem to be working. I think I'm getting confused with the format in which the Node API sends the response and how that works with the Observable type that Angular uses in the service.
Your Angular 2 code looks good to me. But in your Node backend you should not send data with res.end() (see the documentation). Correct would be res.send(files); or in your case res.json(files); which will also set the right Content-Type header.