How to return 202 Accepted and then continue processing the request in Nest.js
Asked Answered
P

2

12

I need a controller action that:

  1. Authorizes the call
  2. Validates it
  3. Returns the 202 Accepted status
  4. Continues processing the request
  5. Makes a call to external API with the results of previously accepted and now processed request.

First two points are easy, I use AuthGuard then class-validator. But I don't know how to return the HTTP response then continue with processing.

As the request consists of an array of (possibly long-running) tasks I thought of using interceptor that uses RxJS to observes the status of tasks and calls external PI upon their completion. However, I have no experience with using RxJS or interceptors (not this way) so I'd really don't know how to leave interceptor's process running but immediately pass control to controller's action.

Also, perhaps there is another, better way? No interceptor but just put all the flow logic in the controller? Some other option?

Prang answered 8/11, 2018 at 10:14 Comment(0)
L
12

I expect you have a service that does the external API call (asynchronously) and returns either a Promise or an Observable:

@Injectable()
export class ExternalApiService {
  constructor(private readonly httpService: HttpService) {}

  makeApiCall(data): Observable<AxiosResponse<any>> {
    return this.httpService.post('https://external.api', data);
  }
}

I also assume that there is a PreprocesserService that makes asynchronous calls (for example getting user information from a database).

Controller

@Post()
@UseGuards(AuthGuard('jwt'))
@HttpCode(HttpStatus.ACCEPTED)
async post(@Body(new ValidationPipe()) myDataDto: MyDataDto) {
  // The preprocessing might throw an exception, so we need to wait for the result
  const preprocessedData = await this.preprocessService.preprocess(myDataDto);
                           ^^^^^
  // We do not need the result to respond to the request, so no await
  this.externalApiService.makeApiCall(preprocessedData);
  return 'Your data is being processed.';
}

When you make asynchronous calls, the execution of your controller method will only wait if you explicitly tell it to by either using async/await or returning a Promise / an Observable.

In this example, we want to wait for the result of the this.preprocessService.preprocess(myDataDto) before we send the response. We do that by using await (the method must be declared as async).

We want to make the request to the external API but we do not need the result for our response. Because we are not the using await here it will make the http call and immediately execute the next line (the return statement) without waiting for the result.

Lesseps answered 9/11, 2018 at 21:2 Comment(4)
If I wanted to use this to call an internal API, I could definitely just do a full http post, but not sure how to invoke my internal observable directly without awaiting it.Nebula
to answer my own comment, my solution is instead of returning the observable to the nest controller and relying on nest to call subscribe behind the scenes, I'm directly calling .subscribe() on the observable and then returning a value right awayNebula
@Nebula Sorry for my late reply Kyle, I was very busy the last couple of weeks. -- Exactly, that's a good option, glad you found a solution so quickly. Alternatively, if you prefer working with promises, you can also call toPromise on the Observable.Lesseps
Hey Kim, thanks very much for the answer and the update. I originally went with toPromise but then got some warnings that toPromise is being deprecated in v7 and removed in v8Nebula
L
7

If your service returns a promise you could do something like the following, the API will return 202 while the processing continues. Of course any processing with the 'then' is happening after 'beginProcessing' is complete.

@Post()
@HttpCode(HttpStatus.ACCEPTED)
beginProcessing(@Req() request, @Body() data: Data): void {
    this.service.process(data).then(...);
}
Lib answered 9/11, 2018 at 15:3 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.