API Node Desacoplada: Criando Interfaces para Requisições e Respostas HTTP
Tags: typescript, node, webdev, api
Published: 2023-08-01 21:25:45

Como mencionado no artigo anterior, nosso controller(CreateSignUpController.ts) está totalmente acoplado. Antes de criar os adaptadores para as rotas vamos alterar nosso controller pra que fique desacoplado e independente de qualquer ferramenta esterna.

## Padronizando as respostas e requisições em uma API Node desacoplada

Vamos definir interfaces para as respostas e requisições de nossa api.

1. Crie uma pasta chamada `shared` dentro da pasta `infra`.
2. Dentro da pasta `shared` crie a pasta `protocols`.
3. Dentro da pasta `protocols` crie o arquivo `http.ts`.

Agora no arquivo `http.ts` insira o código abaixo:

```ts
export type HttpResponse = {
  statusCode: number
  body: unknown
}

export interface HttpRequest {
  body?: unknown
}
```

`HttpResponse`:

A interface `HttpResponse` define a estrutura padrão da resposta que a API enviará para o client após processar uma requisição. Ela possui dois campos:

`statusCode`: É um campo do tipo `number` que representa o código de status HTTP que será enviado como resposta ao client(EX: 200, 201, 400, 500).

`body`: É um campo do tipo `unknown`, o que significa que a resposta pode conter qualquer tipo de dado. Esse campo armazenará o corpo da resposta enviada ao client, que pode ser, por exemplo, um objeto JSON, uma mensagem de sucesso ou erro, ou até mesmo null, caso a resposta não precise conter um corpo.

Utilizando essa interface, a API pode padronizar o formato das respostas enviadas, o que facilita o tratamento das informações pelo client que está consumindo a API.

`HttpRequest`:

A interface `HttpRequest` define a estrutura padrão das requisições que a API receberá do client. Ela possui apenas um campo e é opcional:

`body`: É um campo do tipo `unknown`, que representa o corpo da requisição. Esse campo pode conter qualquer tipo de dado, e é geralmente utilizado para passar dados ao servidor, como parâmetros de busca, dados de formulários, ou payloads em requisições POST/PUT.

Ao definir essa interface, a API estabelece um formato padrão para as requisições que ela espera receber, mas permite que o client envie dados de forma flexível, já que o campo body é opcional. 

Isso permite que a API se adapte a diferentes tipos de requisições sem impor restrições rígidas. Essas duas interfaces, `HttpResponse` e `HttpRequest`, ajudam a promover a separação de responsabilidades e o desacoplamento entre as camadas da aplicação.

## Criando um contrato para nossos controllers

1. Dentro da pasta `protocols` crie o arquivo `controller.ts`
2. No arquivo `controller.ts` insira o seguinte código:

```ts
import { HttpResponse } from './http'

export interface IController<T = unknown> {
  handle(request: T): Promise<HttpResponse>
  
  // O método handle possui a seguinte assinatura:
  // handle(request:T): Promise<HttpResponse>.

  // Ele recebe um parâmetro chamado request do tipo genérico T,
  // que representa o objeto de requisição que será passado para o 
  // controlador. O método retorna uma Promise que resolve o 
  // HttpResponse
}
```

Essa interface `IController` serve como um contrato que deve ser implementado por qualquer classe que atue como um controlador na aplicação. Ao utilizar essa interface, você pode definir vários controladores para diferentes rotas ou recursos da API, mas garantindo que todos eles sigam o mesmo padrão de implementação e retornem uma resposta do tipo `HttpResponse`.

A vantagem dessa abordagem é a padronização e a flexibilidade que ela proporciona. Essa abstração ajuda a manter o código organizado, facilita a criação de novos controladores e torna a API mais flexível, permitindo a fácil substituição ou adição de controladores sem modificar a lógica central da aplicação.

## Criando um contrato para o CREATE do CRUD

1. Dentro da pasta `shared` crie o arquivo `httpHelper.ts`
2. No arquivo `httpHelper.ts` insira o seguinte código:

```ts
import { HttpResponse } from '../protocols/http'

export const create = (data: unknown): HttpResponse => ({
  statusCode: 201,
  body: data
})
```
Uma função `create` é exportada e recebe um parâmetro `data` do tipo `unknown`. O tipo `unknown` indica que a função pode receber qualquer tipo de dado.

A função retorna um objeto do tipo `HttpResponse`, conforme definido pela interface importada na linha acima.

O objeto retornado pela função create possui dois campos:

`statusCode`: Representa o código de status HTTP a ser enviado como resposta ao cliente. Neste caso, o valor 201 indica que a requisição foi bem-sucedida e resultou na criação de um novo recurso (usualmente utilizado após uma operação de criação, como um POST).

`body`: O campo body é definido com o valor do parâmetro `data` recebido pela função. Isso significa que o conteúdo do campo body será igual ao valor do parâmetro `data` passado para a função `create`. Esse campo armazenará o corpo da resposta enviada ao cliente, que pode ser qualquer tipo de dado, incluindo objetos JSON, strings, arrays ou até mesmo null.

A função `create` é útil para padronizar a criação de respostas no formato `HttpResponse`, tornando mais fácil e claro o envio de respostas para o client. Outros contratos podem ser criados para update, read e delete com o statusCode 200.

## Refatorando o controller(CreateSignUpController.ts)

Código antes da refatoração:

Perceba que o controller está acoplado ao `express`. Imagine que a aplicação tem dezenas de controllers, se por acaso for necessário mudar o framework, teriamos muito trabalho pra fazer alterando essa tipagem manualmente em todos os controllers.

Com a refatoração, mesmo se a aplicação tiver dezenas de controllers a alteração a ser feita será apenas na camada de infraestrutura, claro, isso se todos os controllers tiverem sido criados seguindo o contrato definido. 

```ts
import { Response, Request } from "express"
import { CreateSignUpUseCase } from './CreateSignUpUseCase'

interface ICreateSignUpDTO {
  name: string
  password: string
}

export class CreateSignUpController {
  constructor(private readonly useCase: CreateSignUpUseCase) {}

  async handle(req: Request, res: Response): Promise<Response> {
    const { name, password } = req.body as ICreateSignUpDTO

    const data = { name, password }
    await this.useCase.execute(data)

    return res.status(201).send({ message: 'User created successfully' })
  }
}
```

Código refatorado:
```ts
import { CreateSignUpUseCase } from './CreateSignUpUseCase'
import { IController } from '../../../../../infra/shared/protocols/controller'
import { HttpRequest, HttpResponse } from '../../../../../infra/shared/protocols/http'
import { create } from '../../../../../infra/shared/protocols/httpHelper'

interface ICreateSignUpDTO {
  name: string
  password: string
}

export class CreateSignUpController implements IController {
  constructor(private readonly useCase: CreateSignUpUseCase) {}

  async handle(request: HttpRequest): Promise<HttpResponse> {
    const { name, password } = request.body as ICreateSignUpDTO

    const data = { name, password }
    const useCase = await this.useCase.execute(data)

    return create(useCase)
  }
}
```
Esse foi um belo trabalho. Mesmo assim ainda não é possível alternar entre `express` e `fastify`, ainda precisamos criar os adaptadores, o que será feito no próximo artigo.