How to get the form data inside NestJS controller - nestjs

I am in the process of rewriting an existing PHP-based API into NestJS (while learning NestJS at the same time).
Important bit is I cannot touch the frontend service, so I need to make sure that the new app can handle anything the frontend send to it, and respond with the same data structure the original api did.
And here is where I am hitting a wall. Everything submitted by frontend app is in the form of formdata. The only example give in NestJS documentation pertains to getting the uploaded file, but not any additional data that may come with it. Google search was not much of a help so far.
The best I found was this SO post Accept form-data in Nest.js but I am still unable to get the submitted data.
Submitted data:
> POST /public/login HTTP/1.1
> Host: localhost:3000
> Content-Type: multipart/form-data;
> Accept: */*
> Content-Length: 196
| Content-Disposition: form-data; name="login"
| sampleuser
| Content-Disposition: form-data; name="password"
| userpassword
content of my authentication.controller.ts:
import { Controller, Post, Body, Get, Param, Patch, Delete, Req, Header, UseInterceptors, } from "#nestjs/common";
#Controller()
export class AuthenticationController {
constructor() { }
#Post('public/login')
#UseInterceptors()
login(#Body() body) {
console.log(body);
}
}
the log result is simply {}
What am I missing/doing wrong here. Do I need to install some extra npm package to make this work? It surely cannot be that difficult to obtain such common data. Can someone point me to a right direction or a working example.
Thank you

When providing form data (which is mostly for file uploading, or, at least, that's the only way I apply this content type), I think NestJS gives it the same approach. So, I would suggest using the FileInterceptor.
Try this:
import { Controller, Post, Body, Get, Param, Patch, Delete, Req, Header, UseInterceptors, } from "#nestjs/common";
import { FileInterceptor } from '#nestjs/platform-express';
#Controller()
export class AuthenticationController {
constructor() { }
#Post('public/login')
// You're not actually providing a file, but the interceptor will expect "form data"
// (at least in a high level, that's how I think this interceptor works)
#UseInterceptors(FileInterceptor('file'))
login(#Body() body) {
console.log(body);
}
}

You can try this below solution it may help you.
Controller:
import { FileInterceptor } from '#nestjs/platform-express';
#Post('public/login')
#UseInterceptors(FileInterceptor('file'))
public async publicLogin(
#UploadedFile() file: Express.Multer.File,
#Body() body,
) {
// calling service function.
return await this.loginService.publicLogin(file, body);
}
Service:
public async publicLogin(file: Express.Multer.File, body: any): Promise<any> {
console.log(file); // we will get the file object here
console.log(body); // we will get the additiondata of formData.
}

Related

Why I keep getting Bad Request sending valid JSON to an express app using routing controllers?

I have an express app that is setup using routing-controllers. There is only one controller, and it looks like this:
#JsonController('/auth')
export class AuthController {
public authService = new AuthService();
#HttpCode(201)
#Post('/signup')
async signUp(#Body() data: SignUpDto) {
return await this.authService.signup(data);
}
}
And the SignUpDto really doesn't have any validations on it:
export class SignUpDto {
userId: string;
email: string;
password: string;
}
However, when I send a request using cURL:
curl --cacert ./.cert/cert.pem -X POST https://localhost/auth/signup -H 'Content-ype: application/json' -d '{"userId":"01GKYW1JQBCJBXAK0VJTX92C6E","email":"01GKYSZ8AHSE#gmail.com","password":"A1#2e3r4"}'
I get 400 - Bad Request back:
{"name":"BadRequestError","message":"Invalid body, check 'errors' property for more info.","stack":"Error
at new HttpError (/home/user/Work/service/node_modules/routing-controllers/cjs/http-error/HttpError.js:17:22)
at new BadRequestError (/home/user/Work/service/node_modules/routing-controllers/cjs/http-error/BadRequestError.js:10:9)
at /home/user/Work/service/node_modules/routing-controllers/cjs/ActionParameterHandler.js:219:31
at processTicksAndRejections (node:internal/process/task_queues:96:5)
at async ActionParameterHandler.normalizeParamValue (/home/user/Work/service/node_modules/routing-controllers/cjs/ActionParameterHandler.js:134:21)
at async Promise.all (index 0)","errors":[{"target":{"{\"userId\":\"01GKYW1JQBCJBXAK0VJTX92C6E\",\"email\":\"01GKYSZ8AHSE#gmail.com\",\"password\":\"A1#2e3r4\"}":""},"children":[],"constraints":{"unknownValue":"an unknown value was passed to the validate function"}}]}
I have been stuck on this for a few hours... any pointers on what could be causing this?
I found the issue.
I was using a #JsonController but when I sent the request I did not include a Content-Type: application/json header... there was a typo in the header in my request. Without the correct one, routing-controllers doesn't seem to be able to handle the request (sometimes).
Once I added, everything works now.

UnauthorizedException is deliver as Internal Server Error

I'm trying a create a shared Guard as an external library in order to be imported and used across services. I'm not doing anything special that what is described in some guides but with the particularity that the code will reside in a shared library. Everything is working but the Exception to return a 401 error.
My guard looks something like this:
import { Injectable } from '#nestjs/common';
import { AuthGuard } from '#nestjs/passport';
#Injectable()
export class MainGuard extends AuthGuard('jwt') {}
Nothing else. If I use that in a service folder it works, but at the time that I move as in their own library, the response changes.
The way that I'm using in the service has nothing special:
import { MainGuard } from 'shared-guard-library';
import { Controller, Get, UseGuards } from '#nestjs/common';
import { SomeService } from './some.service';
#Controller()
export class SomeController {
constructor(private someService: SomeService) {}
#Get('/foo')
#UseGuards(MainGuard)
async getSomething(): Promise<any> {
return this.someService.getSomething();
}
}
The client receives an error 500:
http :3010/foo
HTTP/1.1 500 Internal Server Error
Connection: keep-alive
Content-Length: 52
Content-Type: application/json; charset=utf-8
Date: Thu, 09 Dec 2021 04:11:42 GMT
ETag: W/"34-rlKccw1E+/fV8niQk4oFitDfPro"
Keep-Alive: timeout=5
Vary: Origin
X-Powered-By: Express
{
"message": "Internal server error",
"statusCode": 500
}
And in the logs shows:
[Nest] 93664 - 12/08/2021, 10:11:42 PM ERROR [ExceptionsHandler] Unauthorized
UnauthorizedException: Unauthorized
at MainGuard.handleRequest (/sharedGuardLibrary/node_modules/#nestjs/passport/dist/auth.guard.js:68:30)
at /sharedGuardLibrary/node_modules/#nestjs/passport/dist/auth.guard.js:49:128
at /sharedGuardLibrary/node_modules/#nestjs/passport/dist/auth.guard.js:86:24
at allFailed (/sharedGuardLibrary/node_modules/passport/lib/middleware/authenticate.js:101:18)
at attempt (/sharedGuardLibrary/node_modules/passport/lib/middleware/authenticate.js:174:28)
at Object.strategy.fail (/sharedGuardLibrary/node_modules/passport/lib/middleware/authenticate.js:296:9)
at Object.JwtStrategy.authenticate (/sharedGuardLibrary/node_modules/passport-jwt/lib/strategy.js:96:21)
at attempt (/sharedGuardLibrary/node_modules/passport/lib/middleware/authenticate.js:360:16)
at authenticate (/sharedGuardLibrary/node_modules/passport/lib/middleware/authenticate.js:361:7)
at /sharedGuardLibrary/node_modules/#nestjs/passport/dist/auth.guard.js:91:3
The logs are telling me that the correct exception was thrown, but is ignored at some point and I don't know the reason. Again: the same code in the same project works.
I took a look at the original class and I don't see any particular way to treat the exception
Any clue or guide it will appreciate.
So, this happens to be a "feature" of Typescript and how JavaScript object equality works in general. So in Nest's BaseExceptionFilter there's a check that exception instanceof HttpException, and normally, UnauthorizedException would be an instance of this, but because this is a library there's a few things that need to be considered.
All of the NestJS dependencies you're using have to be peerDependencies. This makes sure that when the library is installed, there's only one resulting package for the #nestjs/* package.
during local development, you'll need to take care to ensure that you're not resolving multiple instances of the same package (even if it's the exact same version, to JavaScript { hello: 'world' } === { hello: 'world' } // false). To take care of this, things like npm/yarn/pnpm link should not be used, but instead you should copy the dist and the package.json to the main application's node_modules/<package_name> directory.
a. The other option is using a monorepo tool like Nest's monorepo approach or Nx which have single package version approaches, and use the paths of the libraries rather than internal links.
If you follow this, when your production application installs the npm library, everything will work without an issue. It's an annoyance for sure, but it's a side effect of how JavaScript works
I had a similar problem with a custom auth package inside a monorepo.
My auth-library exposed AuthModule, JwtAuthGuard, and some utility functions. All needed packages were installed under my library so any other projects that were using it had not installed other versions of dependencies. Unfortunately using a custom guard caused Internal Server Error.
I've solved this issue by adding a global custom exception filter. It looks for a workaround but at least solves this issue.
This filter is exported from auth-library, so UnauthorizedException indicates on the same object as AuthGuard.
import { ArgumentsHost, Catch, ExceptionFilter, UnauthorizedException } from '#nestjs/common';
import { Response } from 'express'
#Catch(UnauthorizedException)
export class UnauthorizedExceptionFilter implements ExceptionFilter {
public catch(exception: UnauthorizedException, host: ArgumentsHost): Response {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
return response.status(401).json({ statusCode: 401 });
}
}
See Extending Guards: https://docs.nestjs.com/security/authentication#extending-guards
In most cases, using a provided AuthGuard class is sufficient. However, there might be use-cases when you would like to simply extend the default error handling or authentication logic. For this, you can extend the built-in class and override methods within a sub-class.
Implement handleRequest(err, user, info) as follows:
#Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {
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;
}
}

nestjs save each request info without hitting database twice

I want to save each request (path, method, and userId) that comes to the server without having to hit the database twice, and also without messing up the main logic in services files with transactions.
Initially, I was trying to use an interceptor because it gets invoked after auth guards "which attaches the user to request" and before request handlers, but I faced two issues.
first, the fact that the interceptor will call the database to save a new record and then forward the request to handlers which will again hit DB again to handle the request. Secondly, It didn't work anyway because of dependancy injection problems.
code below is not working due to dependency errors as I mentioned, but It will give u an idea about what I need to acheive.
import { Injectable,
NestInterceptor,
Inject,
ExecutionContext,
CallHandler,
HttpException,
HttpStatus } from '#nestjs/common';
import { Observable } from 'rxjs';
import { getRepositoryToken } from '#nestjs/typeorm';
import { Repository } from 'typeorm';
import { HistoryEntity } from '../../modules/history/history.entity';
#Injectable()
export class HistoryInterceptor implements NestInterceptor {
constructor(
#Inject(getRepositoryToken(HistoryEntity))
private readonly historyRepo: Repository<HistoryEntity>
) {}
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const request = context.switchToHttp().getRequest();
const { user, path, method } = request
if (!user) {
throw new HttpException('something terrible happened!', HttpStatus.BAD_GATEWAY);
}
const history = this.historyRepo.create({
path,
userId: user.id,
});
this.historyRepo.save(history);
return next.handle();
}
}
PS. from a performance point of view, It would also be great to not halt the request execution to save these info in db, in other words, Is it ok to NOT use await in this particular situation? because essecntially this is a system related operation and so, Node [rocess shouldn't wait for this step to process and return response to client.
Thanks in advance.

API Controller : Cannot PUT, Cannot DELETE (404 not found)

With Nest.js, and a basic controller :
import { Body, Controller, Delete, Get, Param, Post, Put, Query } from '#nestjs/common';
import { Hero } from '../entities/hero.entity';
import { HeroService } from './hero.service';
#Controller('hero')
export class HeroController {
constructor(private readonly heroesService: HeroService) {}
#Get()
async get(#Query() query): Promise<Hero[]> {
return await this.heroesService.find(query);
}
#Get(':id')
async getById(#Param('id') id): Promise<Hero> {
return await this.heroesService.findById(id);
}
#Post()
async add(#Body() hero: Hero): Promise<Hero> {
return await this.heroesService.save(hero);
}
//TODO: doesn't seem to work, never called (request 404)
#Put(':id')
async update(#Param('id') id, #Body() hero): Promise<Hero> {
console.log('hey');
return await this.heroesService.update(id, hero);
}
//TODO: doesn't seem to work, never called (request 404)
#Delete('/delete/:id')
async remove(#Param('id') id): Promise<Hero> {
console.log('hey');
return await this.heroesService.remove(id);
}
}
Following the basic documentation of nest.js, a module with a controller and a service, injecting a typeorm repository for the entity 'Hero'.
Using Postman, both #Get, #Get(':id') and #Post work perfectly, my entity->repository->service->controller connects to my local Postgres DB, and I can get/add/update data from the Hero table with those API endpoints.
However, PUT and DELETE requests respond with :
{
"statusCode": 404,
"error": "Not Found",
"message": "Cannot PUT /hero"
}
X-Powered-By →Express
Content-Type →application/json; charset=utf-8
Content-Length →67
ETag →W/"43-6vi9yb61CRVGqX01+Xyko0QuUAs"
Date →Sun, 02 Dec 2018 11:40:41 GMT
Connection →keep-alive
The request for this is localhost:3000/hero (same endpoint as GET and POST), i've tried either by adding a id:1 in Params or in the Body with x-www-form-urlencoded.
The requests don't ever seem to arrive at the controller (nothing called), i've added a globalinterceptor to Nest.js that just does this :
intercept(
context: ExecutionContext,
call$: Observable<any>,
): Observable<any> {
console.log(context.switchToHttp().getRequest());
return call$;
}
But again it only logs GET and POST requests, the others never appear.
What confuses me is that I've pretty much followed the Nest.js doc, made a basic controller and service, entity/repository connected to DB, there doesn't seem to be anything else needed for this to work, and yet PUT and DELETE appear to not exist.
Judging from msg Cannot PUT /hero you are making a /hero request rather than for example /hero/1
The request for this is localhost:3000/hero (same endpoint as GET and POST), i've tried either by adding a id:1 in Params or in the Body with x-www-form-urlencoded.
The PUT request should be done with localhost:3000/hero/<id_here> Think you are confusing query params with path params.
Simmilarly DELETE should be done on localhost:3000/hero/delete/<id_here>

Nest.js And Restful CRUD, Why Isn't This Posting?

I have an Angular service that has successfully posted to Firebase and to Postgres through a PHP middleware called DreamFactory. The Angular app works. The problem is in the Nestjs controller #Post() or service add() below. I want to post a json object called recordData. I'm getting an empty object instead of my json data, which is correct in the Angular service. Server console.log results:
recordData in controller: {}
req: {}
recordData in service: {}
The Angular CORS proxy server is working in the Angular dev terminal:
[HPM] POST /api/members -> http://localhost:3000
Angular is using the dev server port 4200, Nestjs is on 3000. The typical development setup.
What's wrong? The payload isn't arriving in the controller. I'm new to server coding.
Angular http.service.ts:
private api = '/api/';
...
public addRecord(dbTable: string, recordData): Observable<any> {
return this.http
.post(`${this.api}${dbTable}`, recordData);
// For this example I'm posting to localhost:3000/api/members db table.
}
My members Nest controller. #Get works, #Post doesn't.
#Controller('api/members') // /members route
export class MembersController {
constructor(private readonly membersService: MembersService) {}
#Get()
async findAll(): Promise<Members[]> {
return await this.membersService.findAll();
}
#Post()
async addItem(#Req() req, #Body() recordData: AddMemberDto) {
console.log('recordData in controller: ', recordData);
console.log('req: ', req.body);
const result: Members = await this.membersService.addItem(recordData);
if (!result)
throw new HttpException('Error adding new Member', HttpStatus.BAD_REQUEST);
return result;
}
There were several problems, some of which I eventually fixed in the edits above. However, the main problem was I needed header info as such. While I had these for other backends they didn't seem to be required for Nestjs. Wrong idea. This is my Angular http.service setup.
private headers = new HttpHeaders()
.set('content-type', 'application/json')
.set('observe', 'response');
public addRecord(dbTable: string, recordData): Observable<any> {
return this.http
.post(`${this.api}${dbTable}`, recordData, {headers: this.headers});
}
I also want to note that many implementations of Nestjs use a dto type for the data param, so recordData: AddMemberDto in the Nestjs controller. I removed it and it works fine.

Resources