Handling HTTP Errors inside services - nestjs

I'm learning Nest, but there is a practice that i don't really like even in the official tutorial. It's one of Handling HTTP specific errors inside services. If later, for some services i'll used a protocol other that HTTP that will use a Service that handle specific HTTP errors, it don't think it's a best practice. As I'm not yet a Nestjs expert, here is how i'm trying to handle this situation:
// errors.interface.ts
export interface IError {
errorCode: number;
errorMessage: string;
}
import { Injectable } from '#nestjs/common';
import { IError } from './errors.interface';
#Injectable()
export class UserService {
// ...
async remove(id: number): Promise<Partial<User> | IError> {
const user = await this.userRepository.findOne({ where: { id } });
if (!user) {
return { errorCode: 404, errorMessage: 'user not found' };
}
await this.userRepository.remove(user);
return {
id,
};
}
}```
Here is my controller.
```// user.controller.ts
import { Controller, Get, HttpException, HttpStatus } from '#nestjs/common';
import { UserService } from './user.service';
import { IError } from './errors.interface';
#Controller('users')
export class UserController {
constructor(private userService: UserService) {}
#Get(':id')
async remove(#Param('id') id: number) {
const result = await this.userService.remove(id);
if ('errorCode' in result) {
throw new HttpException(result.errorMessage, result.errorCode);
}
return result;
}
}
As you can see, I'm trying to handle HTTP-specific errors inside HTTP controllers.
I don't have enough experience with Nestjs, maybe there are better ways to tackle these kinds of problems. I would like to know what is the best practice.

To create a Nest application instance, we use the core NestFactory class. NestFactory exposes a few static methods that allow creating an application instance. The create() method returns an application object, which fulfills the INestApplication interface. This object provides a set of methods which are described in the coming chapters. In the main.ts example above, we simply start up our HTTP listener, which lets the application await inbound HTTP requests.
so you can only work with HTTP requests and handling HTTP specific errors inside services is in fact the best practice.

Related

How to validate dto with class-validator before passing to passportjs AuthGuard

How to validate dto with class-validator before passing to passport AuthGuard? I wanna use class-validator for validating incoming dto, but I see "unathorized" instead of bad request exception because pipes are being evaluated after guards in nestjs. How can I change this behaviour or I have to validate dto right in auth guard?
It's not very in NestJS way, but probably the single option to use is validate DTO inside strategy, that your guard is using:
import { PassportStrategy } from '#nestjs/passport';
import { Strategy } from 'passport-strategy';
import { validate } from 'class-validator';
import { BadRequestException } from '#nestjs/common';
class YourStrategy extends PassportStrategy(Strategy) {
authenticate(req, options) {
const errors = validate(req.body);
if (errors) {
throw new BadRequestException({ errors });
}
super.authenticate(req, options);
}
}

How to implement JSON-Serever in NestJS Application?

I am new to Nest JS Framework and I dont whether I can use json-server to mock external API.
I have already checked NestJS documentation but its not having any example.
I found one question on Stack-overflow but it not complete Example Json-server mocking API instead of calls
I simply want to make one POST call and 1 GET Call.
Dont know which file I write these mocked POST and GET calls.
My first question is, what is your purpose for using NestJS?
You can think of NestJS and json-server as "similar" in their goal; you would use one OR the other. Not both.
If your goal is just to mock data and server, then you have everything you need with json-server. You wouldn't need NestJS.
If what you are looking for is to mock data to retrieve instead of creating a database, you can simply create a simple object in NestJS and retrieve data from there.
For the latter, it might look something like this (not tested):
// app.controller.ts
import { Controller, Get } from '#nestjs/common';
import { AppService } from '../services/app.service';
#Controller('api')
export class AppController {
constructor(private readonly appService: AppService) {}
#Get('/users')
findAllUsers(): Promise<any> {
return this.appService.findAllUsers();
}
}
// app.service.ts
import { Injectable } from '#nestjs/common';
#Injectable()
export class AppService {
private getMockData() {
return {
users: [
{
name: 'john',
email: 'john#doe.com',
},
{
name: 'jane',
email: 'jane#doe.com',
},
],
};
}
findAllUsers(): Promise<any> {
return new Promise(function (resolve, reject) {
const data = this.getMockData();
if (data.users.length > 0) resolve(data.users);
else reject('No users found');
});
}
}
Now you would just have to do the request GET <host>/api/users where the host will be something like http://localhost:8080 in your local machine.

facebook-passport with NestJS

I have looked into both passport-facebook and passport-facebook-token integration with NestJS. The problem is that NestJS abstracts passport implementation with its own utilities such as AuthGuard.
Because of this, ExpressJS style implementation that's documented will not work with NestJS. This for instance is not compliant with the #nestjs/passport package:
var FacebookTokenStrategy = require('passport-facebook-token');
passport.use(new FacebookTokenStrategy({
clientID: FACEBOOK_APP_ID,
clientSecret: FACEBOOK_APP_SECRET
}, function(accessToken, refreshToken, profile, done) {
User.findOrCreate({facebookId: profile.id}, function (error, user) {
return done(error, user);
});
}
));
This blog post shows one strategy for implementing passport-facebook-token using an unfamiliar interface that isn't compliant with AuthGuard.
#Injectable()
export class FacebookStrategy {
constructor(
private readonly userService: UserService,
) {
this.init();
}
init() {
use(
new FacebookTokenStrategy(
{
clientID: <YOUR_APP_CLIENT_ID>,
clientSecret: <YOUR_APP_CLIENT_SECRET>,
fbGraphVersion: 'v3.0',
},
async (
accessToken: string,
refreshToken: string,
profile: any,
done: any,
) => {
const user = await this.userService.findOrCreate(
profile,
);
return done(null, user);
},
),
);
}
}
The problem here is that this seems to be completely unconventional to how NestJS expects you to handle a passport strategy. It is hacked together. It could break in future NestJS updates as well. There's also no exception handling here; I have no way to capture exceptions such as InternalOAuthError which gets thrown by passport-facebook-token because of the callback nature that's being utilized.
Is there a clean way to implement either one of passport-facebook or passport-facebook-token so that it'll use #nestjs/passport's validate() method? From the documentation: For each strategy, Passport will call the verify function (implemented with the validate() method in #nestjs/passport). There should be a way to pass a clientId, clientSecret in the constructor and then put the rest of the logic into the validate() method.
I would imagine the final result to look something similar to the following (this does not work):
import { Injectable } from "#nestjs/common";
import { PassportStrategy } from "#nestjs/passport";
import FacebookTokenStrategy from "passport-facebook-token";
#Injectable()
export class FacebookStrategy extends PassportStrategy(FacebookTokenStrategy, 'facebook')
{
constructor()
{
super({
clientID : 'anid', // <- Replace this with your client id
clientSecret: 'secret', // <- Replace this with your client secret
})
}
async validate(request: any, accessToken: string, refreshToken: string, profile: any, done: Function)
{
try
{
console.log(`hey we got a profile: `, profile);
const jwt: string = 'placeholderJWT'
const user =
{
jwt
}
done(null, user);
}
catch(err)
{
console.log(`got an error: `, err)
done(err, false);
}
}
}
In my particular case, I am not interested in callbackURL. I am just validating an access token that the client has forwarded to the server. I just put the above to be explicit.
Also if you are curious, the code above produces an InternalOAuthError but I have no way of capturing the exception in the strategy to see what the real problem is because it isn't implemented correctly. I know that in this particular case the access_token I am passing is invalid, if I pass a valid one, the code works. With a proper implementation though I would be able to capture the exception, inspect the error, and be able to bubble up a proper exception to the user, in this case an HTTP 401.
InternalOAuthError: Failed to fetch user profile
It seems clear that the exception is being thrown outside of the validate() method, and that's why our try/catch block is not capturing the InternalOAuthError. Handling this exception is critical for normal user experience and I am not sure what the NestJS way of handling it is in this implementation or how error handling should be done.
You're on the right track with the Strategy using extends PassportStrategy() class setup you have going. In order to catch the error from passport, you can extend the AuthGuard('facebook') and add some custom logic to handleRequest(). You can read more about it here, or take a look at this snippet from the docs:
import {
ExecutionContext,
Injectable,
UnauthorizedException,
} from '#nestjs/common';
import { AuthGuard } from '#nestjs/passport';
#Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {
canActivate(context: ExecutionContext) {
// Add your custom authentication logic here
// for example, call super.logIn(request) to establish a session.
return super.canActivate(context);
}
handleRequest(err, user, info) {
// You can throw an exception based on either "info" or "err" arguments
if (err || !user) {
throw err || new UnauthorizedException();
}
return user;
}
}
Yes, this is using JWT instead of Facebook, but the underlying logic and handler are the same so it should still work for you.
In my case, I used to use the passport-facebook-token with older version of nest. To upgrade, the adjustment of the strategy was needed. I am also not interested in the callback url.
This is a working version with passport-facebook-token that uses nest conventions and benefits from dependency injection:
import { Injectable } from '#nestjs/common'
import { PassportStrategy } from '#nestjs/passport'
import * as FacebookTokenStrategy from 'passport-facebook-token'
import { UserService } from '../user/user.service'
import { FacebookUser } from './types'
#Injectable()
export class FacebookStrategy extends PassportStrategy(FacebookTokenStrategy, 'facebook-token') {
constructor(private userService: UserService) {
super({
clientID: process.env.FB_CLIENT_ID,
clientSecret: process.env.FB_CLIENT_SECRET,
})
}
async validate(
accessToken: string,
refreshToken: string,
profile: FacebookTokenStrategy.Profile,
done: (err: any, user: any, info?: any) => void,
): Promise<any> {
const userToInsert: FacebookUser = {
...
}
try {
const user = await this.userService.findOrCreateWithFacebook(userToInsert)
return done(null, user.id) // whatever should get to your controller
} catch (e) {
return done('error', null)
}
}
}
This creates the facebook-token that can be used in the controller.

how to send GET request with parameter

I'm building a simple MEAN application, but facing some problem with a GET method.
I inserted some data in my mongo collection, now I want to GET all results passing it's Id as parameter, but angular is returning me the following:
I've searched about headers and httpParams, but can't seem to find a solution.
I tested on postman as well, using Get and passing a body as JSON, and it worked, I'm facing trouble sending it's body from angular
here's my code:
Angular service
import { Injectable } from '#angular/core';
import { HttpClient, HttpHeaders, HttpParams } from '#angular/common/http';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
#Injectable({
providedIn: 'root'
})
export class WebService {
public api = "http://localhost:3000/";
//NODE API
public getRate(param: any){
console.log(param);
return this.http.get(this.api + "api/rate", {_id: param})
}
}
NodeJs function
module.exports.countVotes = function(req, res) {
console.log(req.body._id);
VoteModel.find({movie_id: req.body._id}, (err, rate) => {
if(err){
console.log('rate not found', err)
return res.status(404).json({
message: 'failed to get movie rate'
})
} else {
res.status(200).json(rate);
console.log(rate);
}
})
}
Node returns me req.body as undefined when called.
What am I missing? please someone help me.
It doesn't work because GET requests don't have request body. You can try use POST request with request body or pass the id to GET request as request param or as path variable.
You must pass an instance of HttpPrams in http.get -
import { Injectable } from '#angular/core';
import { HttpClient, HttpHeaders, HttpParams } from '#angular/common/http';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
#Injectable({
providedIn: 'root'
})
export class WebService {
public api = "http://localhost:3000/";
//NODE API
public getRate(param: any){
let params = new HttpParams().set('_id', param);
console.log(param);
return this.http.get(this.api + "api/rate", { params: params })
}
}

I'm using a passport-jwt auth strategy in my nestJS app (with authGuard), how to get access to the token payload in my controller?

I'm trying to get access to the jwt payload in a route that is protected by an AuthGuard.
I'm using passport-jwt and the token payload is the email of the user.
I could achieve this by runing the code bellow:
import {
Controller,
Headers,
Post,
UseGuards,
} from '#nestjs/common';
import { JwtService } from '#nestjs/jwt';
import { AuthGuard } from '#nestjs/passport';
#Post()
#UseGuards(AuthGuard())
async create(#Headers() headers: any) {
Logger.log(this.jwtService.decode(headers.authorization.split(' ')[1]));
}
I want to know if there's a better way to do it?
Your JwtStrategy has a validate method. Here you have access to the JwtPayload. The return value of this method will be attached to the request (by default under the property user). So you can return whatever you need from the payload here:
async validate(payload: JwtPayload) {
// You can fetch additional information if needed
const user = await this.userService.findUser(payload);
if (!user) {
throw new UnauthorizedException();
}
return {user, email: payload.email};
}
And then access it in you controller by injecting the request:
#Post()
#UseGuards(AuthGuard())
async create(#Req() request) {
Logger.log(req.user.email);
}
You can make this more convenient by creating a custom decorator:
import { createParamDecorator } from '#nestjs/common';
export const User = createParamDecorator((data, req) => {
return req.user;
});
and then inject #User instead of #Req.

Resources