Express delete working but gives not found error

Viewed 305

I am using this boilerplate for my api. It uses express and typeorm with typescript.

When I want want to delete a question, it works fine, but the response is a 404 not found.

Here is my Question.ts class:

@Entity()
export class Question extends BaseEntity {

    @PrimaryColumn('uuid')
    public id: string;

    @IsNotEmpty()
    @Column()
    public title: string;

    @IsNotEmpty()
    @Column({
        length: 2000,
    })
    public description: string;

    @IsNotEmpty()
    @Column()
    public answered: boolean;

    @ManyToOne(type => User, user => user.questions, { onDelete: 'SET NULL', onUpdate: 'CASCADE' })
    public user: User;

    @IsNotEmpty()
    @ManyToMany(type => Tag)
    @JoinTable()
    public tags: Tag[];

    @OneToMany(type => Answer, answer => answer.question)
    public answers: Answer[];

    @OneToMany(type => Comment, comment => comment.question)
    public comments: Comment[];
}

Here is my request in the controller:

@Delete('/:id')
public delete(@Param('id') id: string): Promise<void> {
    return this.questionService.delete(id);
}

Here is my method in the service:

public async delete(id: string): Promise<void> {
    this.log.info('Delete questions: ', id);
    try {
        await Question.delete(id);
    } catch (error) {
        this.log.error('Could not delete question: ', id, ' Message: ', error);
    }
}

In the console I get the following error: console error

When I get all the questions after the delete, the question isn't there anymore, so the question has been succesfully deleted. Why am I getting a 404 although my delete works?

Update

On Request of aRvi, here is the full file of the controller:

import { Request } from 'express';
import {
    Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put, QueryParam, Req
} from 'routing-controllers';
import { ResponseSchema } from 'routing-controllers-openapi';

import { QuestionNotFoundError } from '../errors/QuestionNotFoundError';
import { Answer, Comment, PagedResult, Question, Tag, User } from '../models/Models';
import { QuestionService } from '../services/Services';
import { CreateQuestionBody } from './Bodies/CreateQuestionBody';
import { PutQuestionBody } from './Bodies/PutQuestionBody';
import { QuestionResponse } from './responses/Responses';

@JsonController('/questions')
export class QuestionController {
    constructor(
        private questionService: QuestionService
    ) { }

    @Get()
    // tslint:disable-next-line:max-line-length
    public async find(@Req() req: Request, @QueryParam('skip') skip: number, @QueryParam('take') take: number, @QueryParam('orderBy') orderBy: string, @QueryParam('where') where: string): Promise<PagedResult<Question>> {
        const questions = await this.questionService.find();

        return new PagedResult<Question>().Create(req, questions, skip, take, orderBy, where);
    }

    @Get('/:id')
    @ResponseSchema(QuestionResponse)
    @OnUndefined(QuestionNotFoundError)
    public findOne(@Param('id') id: string): Promise<Question | undefined> {
        return this.questionService.findOne(id);
    }

    @Get('/:id/answers')
    @OnUndefined(QuestionNotFoundError)
    // tslint:disable-next-line:max-line-length
    public async findAnswers(@Req() req: Request, @Param('id') id: string, @QueryParam('skip') skip: number, @QueryParam('take') take: number, @QueryParam('orderBy') orderBy: string, @QueryParam('where') where: string): Promise<PagedResult<Answer> | undefined> {
        const answers = await this.questionService.findAnswers(id);

        return new PagedResult<Answer>().Create(req, answers, skip, take, orderBy, where);
    }

    @Get('/:id/comments')
    @OnUndefined(QuestionNotFoundError)
    // tslint:disable-next-line:max-line-length
    public async findComments(@Req() req: Request, @Param('id') id: string, @QueryParam('skip') skip: number, @QueryParam('take') take: number, @QueryParam('orderBy') orderBy: string, @QueryParam('where') where: string): Promise<PagedResult<Comment> | undefined> {
        const comments = await this.questionService.findComments(id);

        return new PagedResult<Comment>().Create(req, comments, skip, take, orderBy, where);
    }

    @Get('/:id/tags')
    @OnUndefined(QuestionNotFoundError)
    // tslint:disable-next-line:max-line-length
    public async findTags(@Req() req: Request, @Param('id') id: string, @QueryParam('skip') skip: number, @QueryParam('take') take: number, @QueryParam('orderBy') orderBy: string, @QueryParam('where') where: string): Promise<PagedResult<Tag> | undefined> {
        const tags = await this.questionService.findTags(id);

        return new PagedResult<Tag>().Create(req, tags, skip, take, orderBy, where);
    }

    @Post()
    @ResponseSchema(QuestionResponse)
    public async create(@Body() body: CreateQuestionBody): Promise<Question | undefined> {
        const question = new Question();
        question.title = body.title;
        question.description = body.description;
        question.tags = body.tags;
        // TODO: change to current user
        const users = await User.find();
        const user = users[0];
        question.user = user;
        return this.questionService.create(question);
    }

    @Put()
    @ResponseSchema(QuestionResponse)
    public updateMerge(@Body() body: PutQuestionBody): Promise<Question | undefined> {
        return this.questionService.updateMerge(body);
    }

    @Post('/:id')
    @ResponseSchema(QuestionResponse)
    public updateOne(@Param('id') id: string, @Body() body: CreateQuestionBody): Promise<Question | undefined> {
        const question = new Question();
        question.title = body.title;
        question.description = body.description;
        question.tags = body.tags;

        return this.questionService.updateFull(id, question);
    }

    @Delete('/:id')
    public delete(@Param('id') id: string): Promise<void> {
        return this.questionService.delete(id);
    }
}

4 Answers

From the error logs you posted, I found that this error is being thrown from ExpressDriver.ts file. So, on examining the file from github, I saw this piece of logic.

enter image description here

So, when the returned result is undefined and if no undefinedResultCode is mentioned, it will throw NotFoundError() resulting in 404 status code.

Try returning some response from it and see if the issue still persists or add

@UndefinedResultCode(200)

to the top of the delete function.

Well, your questionService.delete(id) method is not returning anything, so the delete handler is returning undefined and I suppose Express thinks that the endpoint is unhandled, thus it returns a 404. Try to return something from your questionService, that is:

try {
  await Question.delete(id);
  return 'OK' // add this
} catch (error) {
  this.log.error('Could not delete question: ', id, ' Message: ', error);
}

or force an empty response (HTTP 204) in the delete route handler

I recently had an incursion. @Jerin D Joy kind of brought me to this, so thank you for that. The boilerplate uses routing-controllers. So I looked into the options class in the repository and found, that you can override the default response code on undefined returns. The default setting of course is 404. I changed this to 200 and with some slight modifications this works perfect for me. My configuration as of the current looks like the following:

const app: Application = createExpressServer({
  cors: true,
  routePrefix: env.app.routePrefix,
  controllers: env.app.dirs.controllers,
  middlewares: env.app.dirs.middlewares,
  classTransformer: true,
  authorizationChecker: authorizationChecker(connection),
  currentUserChecker: currentUserChecker(connection),
  defaults: {
    undefinedResultCode: 200,
  },
});

This solves my Issue, since on every request I define a custom error when the response is undefined. The only thing I have to do is to remove the @OnUndefined decorator from the delete requests.

Note: I had to change some requests, so if you adopt my solution, check which requests you need to adjust.

Note2: The solution of course could also be solved by just returning a string like Successfully deleted object or so but I prefer the solution above, since I think it's the better best practice.

@Delete('/:id')
public delete(@Param('id') id: string): Promise<void> {
    return await this.questionService.delete(id);
}

Your using an async function without waiting or it. You receive some <pending> object so it might cause problem.

Related