API Controller : Cannot PUT, Cannot DELETE (404 not found) - node.js

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>

Related

In NestJs can I use an interceptor and the #Response parameter status setter?

I have a NestJs application that uses an interceptor to wrap all http responses much like the example in the docs https://docs.nestjs.com/interceptors ...
#Injectable()
export class TransformInterceptor<T> implements NestInterceptor<T, Response<T>> {
intercept(context: ExecutionContext, next: CallHandler): Observable<Response<T>> {
return next.handle().pipe(map(data => ({ data })));
}
}
For most endpoints this is good enough, but I've found some instances where I want to set the response code based on some logic in the controller, e.g.:
#UseInterceptors(TransformInterceptor)
#Post('some-path')
async someFunction(
#Response() reply: Fastify.FastifyReply,
) {
return reply
.status(isATeapot ? HttpStatus.I_AM_A_TEAPOT : HttpStatus.OK)
.send(someData);
}
But, in this latter case the interceptor gets called with undefined and the response that hits the wire is just the raw data.
Is there a way to get both the interceptor and the branching in the status code?
Use #Response({ passthrough: true }) and don't .send() the response, to be able to just set the HTTP code and let Nest still handle the response
#UseInterceptors(TransformInterceptor)
#Post('some-path')
async someFunction(
#Response({ passthrough: true }) reply: Fastify.FastifyReply,
) {
reply
.status(isATeapot ? HttpStatus.I_AM_A_TEAPOT : HttpStatus.OK)
return someData;
}

How to get the form data inside NestJS controller

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.
}

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.

Problem with Authentication chapter and the 19-auth sample

I downloaded the 19-auth sample and add some console.log debug code to it, then found some problems.
The code in JwtAuthGuard is never executed: '2222222' was not printed to the console in the code below:
canActivate(context: ExecutionContext) {
console.log('22222222222');
// add your custom authentication logic here
// for example, call super.logIn(request) to establish a session.
return super.canActivate(context);
}
When I changed the guard to JwtAuthGuard in the AuthController:
#get('data')
#UseGuards(JwtAuthGuard)
findAll(#Req() req) {
return req.user;
// this route is restricted by AuthGuard
// JWT strategy
}
the code in JwtAuthGuard was invoked, but in the canActivate function, I can't get the user info from request. and the canActivate function was called before the JwtStrategy?
Can someone explain how the code executing for the auth module, and how to get the user info in the JwtAuthGuard?
paste the latest code and console log here:
JwtStrategy
/**
* jwt passport 调用validate方法来判断是否授权用户进行接口调用
* #param payload
*/
async validate(payload: AuthPayload) {
Logger.log(`payload is ${JSON.stringify(payload)}`, 'JwtStrategy');
const user = await this.authService.validateUser(payload.id);
if (!user) {
throw new UnauthorizedException('不存在的用户信息');
}
return user;
}
JwtAuthGuard
canActivate(context: ExecutionContext) {
// add your custom authentication logic here
// for example, call super.logIn(request) to establish a session.
// this.accessPriv = this.reflector.get<string>('accessPriv', context.getHandler());
console.log('canActivate executed 111111111111111111');
return super.canActivate(context);
}
and the console log as below:
canActivate executed 111111111111111111
[Nest] 14080 - 2019-04-01 11:19 [JwtStrategy] payload is {"userName":"fanliang","id":"1","iat":1553772641,"exp":1554377441} +2286ms
it seems that the canActivate() function of JwtAuthGuard executed before the validate() function of JwtStrategy, but the user info was attached to the request after JwtStrategy validate().
what I want is to get the user info from request in the canActivate() of custom AuthGuard such like JwtAuthGuard
I have a somewhat solution that works for me.
Calling the super.canActivate before my own logic.
seems like the population of req.user triggered by it.
An example:
import { ExecutionContext, Injectable } from "#nestjs/common";
import { AuthGuard } from "#nestjs/passport";
import { Request } from "express";
#Injectable()
export class AuthGuardWithAllowSentry extends AuthGuard("jwt") {
public async canActivate(context: ExecutionContext) {
// that code will call the passport jwt
const origCanActivate = await super.canActivate(context);
// now we have request.user!
const http = context.switchToHttp();
const request = http.getRequest<Request>();
console.log(request.user)
if (request.header("X-Sentry-Token") === "blablabla") {
if (request.method === "GET" && request.path.endsWith(".map")) {
return true;
}
}
// some random logic
return request.user.roles.includes("admin")
}
}
it feels for me more like a workaround than a real thing.
I agree that the 19-auth sample is a little bit confusing to follow. This is mainly because it includes the JWTAuthGuard (as a reference for building custom guards) but it is never actually used. Instead, the original use of plain AuthGuard is already set up to provide JWT functionality. However, both guards leverage the JWTStrategy. If you want to understand this better, you could try updating your AuthController:
#Get('data')
#UseGuards(AuthGuard())
findAll() {
// this route is restricted by AuthGuard
// JWT strategy
return {
message: 'Successfully passed AuthGuard',
};
}
#Get('custom-jwt')
#UseGuards(new JwtAuthGuard())
// this route is restricted by JWTAuthGuard custom
// JWT strategy
customJwt() {
return {
message: 'Successfully passed JWTAuthGuard',
};
}
The important part is that in order to get past either guard, you must send the request with the Authorization header properly set to the token that's returned from the token endpoint.
For example: Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6InRlc3RAZW1haWwuY29tIiwiaWF0IjoxNTU0MDUyNDczLCJleHAiOjE1NTQwNTYwNzN9.3Q8_FC-qFXk1F4KmMrHVSmmNGPAyHdt2myr5c18_E-U
I find it easiest to use a tool like Postman or Insomnia for constructing requests and setting Headers, etc but you could also use CURL. Once you've set the Authorization header with a valid token you'll be able to hit both of the guarded endpoints. If you put a console.log in the JWTStrategy you'll see that both guards end up using the validate method to retrieve the user correctly.

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