NestJS tip: platform-agnostic way to define response status/headers
Tags: nestjs, node, programming
Published: 2022-11-20 19:50:47

## TL;DR

> for NestJS v8, v9 and v10

If you want to change the default status code dynamically, or want to define HTTP headers dynamically, you don't have to switch to the [library-specific mode](https://docs.nestjs.com/controllers#library-specific-approach)!

You could have something like this:

```ts
import { Controller, Get } from '@nestjs/common'
import { GenericResponse } from './generic-response.decorator'
//       ^~ this is new
@Controller()
export class AppController {
  @Get()
  getHello(@GenericResponse() res: GenericResponse): string {
    res
      .setHeader('X-Header', 'foo') // <<
      .setStatus(201) // <<

    return 'Hello World'
  }
}
```

## What?

Sometimes we need to change the status code of the response in controller's method with some dynamic value. To do so, you could do the following:

```ts
import { Res } from '@nestjs/common'
import type { Response } from 'express'
// ...
@Get()
findAll(@Res({ passthrough: true }) res: Response) {
  const statusCode = something ? 200 : 201
  res.status(statusCode) // <- Using library-specific response object's method
  // ...
}
```

As you can see above, since we're using the `@Res()` (or `@Response()`) decorator) we've switched to the [library-specific mode](https://docs.nestjs.com/controllers#library-specific-approach). Thus, the `res` parameter will be an object created by the underlying HTTP server _(ExpressJS, in this example)_.

By taking that approach, our NestJS app starts breaking its well known platform agnosticism. So we won't be able to switch to any other HTTP adapter easily that doesn't follows the same API for response object as Express without changing others parts of the source code other than `main.ts` :disappointed: 

The same applies for HTTP headers (`@Header()`) too. The only way to define a header dynamically at the controller level is by using the lib-specific response object -- in this case it would be `res.set('X-Header', 'Foo')`

## Solution

To me, a better solution for such problem will be having another abstraction layer on top of the response object (what I've called `AbstractResponse`). We can leverage on [execution context](https://docs.nestjs.com/fundamentals/execution-context#execution-context) object to retrieve the current response object, while using the [`HttpAdapterHost`](https://docs.nestjs.com/faq/http-adapter#in-context-strategy) to access high-level operations that are implemented by the HTTP adapter. Finally, we can use [Pipes](https://docs.nestjs.com/pipes) along with [`createParamDecorator`](https://docs.nestjs.com/custom-decorators#param-decorators) utility to build the brigde between all of that while making the interface for this new behavior looks better.

Now we are no longer manipulating the library-specific response object :partying_face: which will make our code more reusable across different HTTP adapters (or even other kinds of adapters, if you play with that a bit)

Here's the full code in a playground: https://stackblitz.com/edit/nestjs-abs-res

---

- `app.controller.ts`

```ts
import { Controller, Get } from '@nestjs/common';
import { GenericResponse } from './generic-response.decorator';

@Controller()
export class AppController {
  @Get()
  getHello(@GenericResponse() res: GenericResponse): string {
    res
      .setHeader('X-Header', 'foo')
      .setStatus(201);

    return 'Hello';
  }
}
```

- `generic-response.decorator.ts`

```ts
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
import { AbstractResponse } from './abstract-response';
import { ExecutionContextToAbstractResponsePipe } from './ctx-to-abstract-response.pipe';

const GetExecutionContext = createParamDecorator(
  (
    _: never,
    ctx: ExecutionContext,
  ): ExecutionContext => ctx,
);

export const GenericResponse = (): ParameterDecorator =>
  GetExecutionContext(ExecutionContextToAbstractResponsePipe);

export type GenericResponse = AbstractResponse;
```

- `ctx-to-abstract-response.pipe.ts`

```ts
import { Injectable, PipeTransform, ExecutionContext } from '@nestjs/common';
import { HttpAdapterHost } from '@nestjs/core';
import { AbstractResponse } from './abstract-response'

@Injectable()
export class ExecutionContextToAbstractResponsePipe implements PipeTransform {
  constructor(
    private readonly httpAdapterHost: HttpAdapterHost,
  ) {}

  transform(ctx: ExecutionContext): AbstractResponse {
    return new AbstractResponse(this.httpAdapterHost.httpAdapter, ctx);
  }
}
```

- `abstract-response.ts`

```ts
import type { ExecutionContext } from '@nestjs/common';
import type { HttpArgumentsHost } from '@nestjs/common/interfaces';
import { AbstractHttpAdapter } from '@nestjs/core';

export class AbstractResponse { // Note that this isn't a provider
  httpCtx: HttpArgumentsHost;

  constructor(
    private readonly httpAdapter: AbstractHttpAdapter,
    readonly executionContext: ExecutionContext,
  ) {
    this.httpCtx = executionContext.switchToHttp();
  }

  /** Define the HTTP header on the supplied response object. */
  setHeader(name: string, value: string): this {
    this.httpAdapter.setHeader(this.httpCtx.getResponse(), name, value);
    return this;
  }

  /** Define the HTTP status code on the supplied response object. */
  setStatus(statusCode: number): this {
    this.httpAdapter.status(this.httpCtx.getResponse(), statusCode);
    return this;
  }
}
```

### A minimal solution

Do note that all of this could be also accomplished by just injecting the `HttpAdapterHost` into the controller and calling the desired methods of the `httpAdapterHost.httpAdapter` object like so:

```ts
import { Controller, Get, Res } from '@nestjs/common'
import { HttpAdapterHost } from '@nestjs/core'
import type { Response } from 'express'

@Controller()
export class AppController {
  constructor(private readonly httpAdapterHost: HttpAdapterHost<ExpressAdapter>) {}

  @Get()
  findAll(@Res({ passthrough: true }) res: Response) {
    const { httpAdapter } = this.httpAdapterHost
    httpAdapter.setHeader(res, 'X-Header', 'Foo')
    httpAdapter.status(res, 201)
    // ...
  }
}
```

---

Thanks to @jmcdo29 for reviewing this article.