Os controladores são responsáveis por lidar com as entradas de requisições e retornando respostas para o cliente.
O objetivo de um controlador é receber solicitações específicas para o aplicativo. O mecanismo de controle roteamento que direciona quais controller irão receber as solicitações. Freqüentemente, cada controlador possui mais de uma rota e rotas diferentes podem executar ações diferentes.
Para criar um controlador básico, usamos classes e decoradores. Os decoradores associam as classes aos metadados necessários e permitem que o Nest crie um mapa de roteamento (para os controladores correspondentes).
DICA
Para criar rapidamente um controlador CRUD com a validação embutida, você pode usar as CLI's Gerador CRUD: nest g resource [name].
No exemplo a seguir, usaremos o decorador @Controller()
, que é requerido para definir um controlador básico. Especificaremos um prefixo opcional do caminho da rota de cats
. Usando um prefixo de caminho em um decorador @Controller()
nos permite agrupar facilmente um conjunto de rotas relacionadas e minimizar o código repetitivo. Por exemplo, podemos optar por agrupar um conjunto de rotas que gerenciam interações com uma entidade de cat
sob a rota /cats
. Nesse caso, poderíamos especificar o prefixo do caminho cats
no decorador @Controller()
para que não tenhamos que repetir essa parte do caminho para cada rota no arquivo.
import { Controller, Get } from '@nestjs/common';
@Controller('cats')
export class CatsController {
@Get()
findAll(): string {
return 'This action returns all cats';
}
}
DICA
Para criar um controlador usando a CLI, basta executar o comando
$ nest g controller cats
.
O decorador @Get()
do método de solicitação HTTP, que está escrito antes do método findAll()
informa ao Nest para criar um manipulador para um endpoint específico para solicitações HTTP. O endpoint corresponde ao método de solicitação HTTP (GET, neste caso) e ao caminho da rota. Qual é o caminho da rota? O caminho da rota para um manipulador é determinado concatenando o prefixo (opcional) declarado para o controlador e qualquer caminho especificado no decorador do método. Desde que declaramos um prefixo para cada rota (cats
) e não adicionou nenhuma informação de caminho no decorador, o Nest mapeará requisições GET /cats
para este manipulador. Como mencionado, o caminho inclui o prefixo opcional do caminho do controlador e qualquer sequência de caminho declarada no decorador do método de solicitação. Por exemplo, um prefixo de caminho de cats
combinado com o decorador @Get('breed')
produziria um mapeamento de rota para solicitações como GET /cats/breed
.
Em nosso exemplo acima, quando uma solicitação GET é feita para esse endpoint, a Nest encaminha a solicitação para o método findAll()
definido pelo usuário. Observe que o nome do método que escolhemos aqui é completamente arbitrário. Obviamente, devemos declarar um método para vincular a rota, mas Nest não atribui nenhum significado ao nome do método escolhido.
Esse método retornará um código de status 200 e a resposta associada, que neste caso é apenas uma sequência. Por que isso acontece? Para explicar, primeiro apresentaremos o conceito de que a Nest emprega duas diferentes opções para manipular respostas:
Padrão (recomendado) |
Usando esse método embutido, quando um manipulador de solicitações retorna um objeto ou matriz JavaScript, ele retornará automaticamente ser serializado para JSON. Quando retorna um tipo primitivo de JavaScript (por exemplo. string , number , boolean ), no entanto, o Nest enviará apenas o valor sem tentar serializá-lo. Isso simplifica o tratamento da resposta: basta retornar o valor e o Nest cuida do resto.
Além disso, a resposta código de status é sempre 200 por padrão, exceto para solicitações POST que usam 201. Podemos mudar facilmente esse comportamento adicionando o decorador |
Específico da biblioteca |
Podemos usar o (específico da biblioteca, por exemplo, Express) objeto de resposta, que pode ser injetado usando o decorador @Res() na assinatura do manipulador de métodos (por exemplo findAll(@Res() response) ). Com essa abordagem, você pode usar os métodos nativos de tratamento de respostas expostos por esse objeto. No caso com o Express, você pode construir respostas usando código como response.status(200).send() .
|
AVISO O Nest detecta quando o manipulador está usando
@Res()
ou@Next()
, indicando que você escolheu a opção específica da biblioteca. Se ambas as abordagens forem usadas ao mesmo tempo, a abordagem padrão será desativada automaticamente para esta rota única e não funcionará mais como esperado. Para usar as duas abordagens ao mesmo tempo (por exemplo, injetando o objeto de resposta apenas para definir cookies/cabeçalhos, mas ainda deixando o restante na estrutura), você deve definir a opçãopassthrough
paratrue
no decorador@Res({ passthrough: true })
.
Os manipuladores geralmente precisam acessar os detalhes da requisição do cliente. Nest fornece acesso ao objeto de solicitação da plataforma subjacente (Express por padrão). Podemos acessar o objeto de solicitação instruindo o Nest a injetá-lo adicionando o decorador @Req()
para a assinatura do manipulador.
// cats.controller.ts
import { Controller, Get, Req } from '@nestjs/common';
import { Request } from 'express';
@Controller('cats')
export class CatsController {
@Get()
findAll(@Req() request: Request): string {
return 'This action returns all cats';
}
}
DICA Para tirar proveito dos types do
express
(comorequest: Request
no exemplo acima), instale o pacote@types/express
.
O objeto de solicitação representa a solicitação HTTP e possui propriedades para a sequência de querystrings, parâmetros, cabeçalhos HTTP e corpo (leia mais aqui). Na maioria dos casos, não é necessário pegar essas propriedades manualmente. Em vez disso, podemos usar decoradores dedicados, como @Body()
ou @Query()
, que estão disponíveis fora da caixa. Abaixo está uma lista dos decoradores fornecidos e dos objetos simples específicos da plataforma que eles representam.
@Request() , @Req() |
req |
@Response() , @Res()* |
res |
@Next() |
next |
@Session() |
req.session |
@Param(key?: string) |
req.params /req.params[key] |
@Body(key?: string) |
req.body /req.body[key] |
@Query(key?: string) |
req.query /req.query[key] |
@Headers(name?: string) |
req.headers /req.headers[name] |
@Ip() |
req.ip |
@HostParam() |
req.hosts |
Para compatibilidade com tipagens nas plataformas HTTP subjacentes (por exemplo, Express e Fastify), o Nest fornece os decoradores @Res()
e @Response()
. @Res()
é simplesmente um apelido para @Response()
. Ambos expõem diretamente a interface do objeto response nativo. Ao usá-los, você também deve importar as tipagens para a biblioteca subjacente (por exemplo @types/express
) para tirar o máximo proveito. Observe que quando você injeta @Res()
ou @Response()
em um manipulador de métodos, você coloca o Nest em Modo específico da biblioteca para esse manipulador e você se torna responsável por gerenciar a resposta. Ao fazer isso, você deve emitir algum tipo de resposta retornando para o objeto response
(por exemplo res.json(...)
ou res.send(...)
) ou o servidor HTTP será suspenso.
DICA
Para aprender a criar seus próprios decoradores personalizados, visite este capítulo.
Anteriormente, definimos um ponto final para buscar o recurso de cats (GET rota). Normalmente, também queremos fornecer um endpoint que crie novos registros. Para isso, vamos criar o manipulador de POST:
// cats.controller.tsJS
import { Controller, Get, Post } from '@nestjs/common';
@Controller('cats')
export class CatsController {
@Post()
create(): string {
return 'This action adds a new cat';
}
@Get()
findAll(): string {
return 'This action returns all cats';
}
}
É simples assim. Nest fornece decoradores para todos os métodos HTTP padrão: @Get()
, @Post()
, @Put()
, @Delete()
, @Patch()
, @Options()
, e @Head()
. Além disso, @All()
define um endpoint que lida com todos eles.
Rotas baseadas em padrões também são suportadas. Por exemplo, o asterisco é usado como um curinga e corresponde a qualquer combinação de caracteres.
@Get('ab*cd')
findAll() {
return 'This route uses a wildcard';
}
O caminho de rota 'ab*cd'
corresponderá a abcd
, ab_cd
, abecd
, e assim por diante. Os caracteres ?
, +
, *
e ()
podem ser usado em um caminho de rota e são subconjuntos de suas contrapartes de expressão regular. O hífen (-
) e o ponto (.
) são interpretados literalmente por caminhos baseados em string.
Como mencionado, a resposta de código de status é sempre 200 por padrão, exceto para solicitações POST que são 201. Podemos mudar facilmente esse comportamento adicionando o decorador @HttpCode(...)
no nível de um manipulador.
@Post()
@HttpCode(204)
create() {
return 'This action adds a new cat';
}
DICA Importe
HttpCode
do pacote@nestjs/common
.
Muitas vezes, seu código de status não é estático, mas depende de vários fatores. Nesse caso, você pode usar uma resposta da biblioteca específica (injetar usando o objeto @Res()
) (ou, em caso de erro, lança uma exceção).
Para especificar um cabeçalho de resposta personalizado, você pode usar um decorador @Header()
ou um objeto de resposta específico da biblioteca (invocando a chamada res.header()
diretamente).
@Post()
@Header('Cache-Control', 'none')
create() {
return 'This action adds a new cat';
}
DICA Importe
Header
do pacote@nestjs/common
.
Para redirecionar uma resposta para um URL específico, você pode usar um decorador @Redirect()
ou um objeto de resposta específico da biblioteca (invocando o método res.redirect()
diretamente).
@Redirect()
leva dois argumentos: url
e statusCode
, ambos são opcionais. O valor padrão de statusCode é 302 (Found)
se omitido.
@Get()
@Redirect('https://nestjs.com', 301)
Às vezes, convém determinar o código de status HTTP ou o URL de redirecionamento dinamicamente. Faça isso retornando um objeto do método de manipulador de rota com a forma:
{
"url": string,
"statusCode": number
}
Os valores retornados substituirão todos os argumentos passados para o decorador @Redirect()
. Por exemplo:
@Get('docs')
@Redirect('https://docs.nestjs.com', 302)
getDocs(@Query('version') version) {
if (version && version === '5') {
return { url: 'https://docs.nestjs.com/v5/' };
}
}
Rotas com caminhos estáticos não funcionarão quando você precisar aceitar dados dinâmicos como parte da solicitação (por exemplo GET /cats/1
para obter cat com id 1
). Para definir rotas com parâmetros, podemos adicionar parâmetros de rota como tokens no caminho da rota para capturar o valor dinâmico nessa posição no URL da solicitação. O parâmetro de token no decorador @Get()
será exemplificado abaixo. Os parâmetros de rota declarados dessa maneira podem ser acessados usando o decorador @Param()
, que deve ser adicionado à assinatura do método.
DICA Rotas com parâmetros devem ser declaradas após quaisquer caminhos estáticos. Isso impede que os caminhos parametrizados interceptem o tráfego destinado aos caminhos estáticos.
@Get(':id')
findOne(@Param() params): string {
console.log(params.id);
return `This action returns a #${params.id} cat`;
}
@Param()
é usado para decorar um parâmetro de método (params
no exemplo acima), e faz os parâmetros da rota disponíveis como propriedades desse método decorado dentro do corpo do método. Como visto no código acima, podemos acessar o parâmetro id
referenciando params.id
. Você também pode passar um parâmetro específico para o decorador e, em seguida, fazer referência ao parâmetro route diretamente pelo nome no corpo do método.
DICA Importe
Param
do pacote@nestjs/common
.
@Get(':id')
findOne(@Param('id') id: string): string {
return `This action returns a #${id} cat`;
}
O decorador @Controller
pode levar uma opção host
para exigir que o host HTTP das solicitações recebidas corresponda a algum valor específico.
@Controller({ host: 'admin.example.com' })
export class AdminController {
@Get()
index(): string {
return 'Admin page';
}
}
AVISO Fastify não possui suporte para roteadores aninhados; ao usar o roteamento de subdomínio, o adaptador do Express padrão deve ser usado.
Semelhante a um caminho de rota, A opção hosts
pode usar tokens para capturar o valor dinâmico nessa posição no nome do host. O parâmetro com token no @Controller()
será exemplificado abaixo. Os parâmetros do host declarados dessa maneira podem ser acessados usando o decorador @HostParam()
, que deve ser adicionado à assinatura do método.
@Controller({ host: ':account.example.com' })
export class AccountController {
@Get()
getInfo(@HostParam('account') account: string) {
return account;
}
}
Para pessoas provenientes de diferentes contextos de linguagem de programação, pode ser inesperado saber que, no Nest, quase tudo é compartilhado entre as solicitações recebidas. Temos um pool de conexões com o banco de dados, serviços singleton com estado global etc. Lembre-se de que o Node.js não segue o modelo sem estado multiencadeado de solicitação/resposta no qual cada solicitação é processada por um encadeamento separado. Portanto, o uso de instâncias singleton é totalmente seguro para nossas aplicações.
No entanto, existem casos de borda quando a vida útil do controlador baseada em solicitação pode ser o comportamento desejado, por exemplo, cache por solicitação em aplicativos GraphQL, rastreamento de solicitação ou multitenização. Aprenda a controlar escopos aqui.
Adoramos o JavaScript moderno e sabemos que a extração de dados é principalmente assíncrono. É por isso que o Nest suporta e funciona bem com funções async
.
DICA Saiba mais sobre async/await aqui
Toda função async precisa retornar um Promise
. Isso significa que você pode retornar um valor diferido que a Nest poderá resolver sozinha. Vamos ver um exemplo disso:
// cats.controller.ts
@Get()
async findAll(): Promise<any[]> {
return [];
}
O código acima é totalmente válido. Além disso, os manipuladores de rota Nest são ainda mais poderosos ao poder retornar o RxJS streams observáveis. O Nest se inscreve automaticamente na fonte abaixo e assume o último valor emitido (depois que o stream for concluído).
// cats.controller.ts
@Get()
findAll(): Observable<any[]> {
return of([]);
}
Ambas as abordagens acima funcionam e você pode usar o que for adequado às suas necessidades.
Nosso exemplo anterior do manipulador de rotas POST não aceitou nenhum parâmetro do cliente. Vamos corrigir isso adicionando o decorador @Body()
aqui.
Mas primeiro (se você usa TypeScript), precisamos determinar o esquema DTO (Objeto de transferência de dados). Um DTO é um objeto que define como os dados serão enviados pela rede. Poderíamos determinar o esquema DTO usando interfaces TypeScript ou por classes simples. Curiosamente, recomendamos o uso de classes aqui. Por quê? As classes fazem parte do padrão JavaScript ES6 e, portanto, são preservadas como entidades reais no JavaScript compilado. Por outro lado, como as interfaces TypeScript são removidas durante a transpilação, o Nest não pode se referir a elas em tempo de execução. Isso é importante porque recursos como Pipes tem possibilidades adicionais quando tiverem acesso ao metatipo da variável em tempo de execução.
Vamos criar a classe CreateCatDto
:
// create-cat.dto.ts
export class CreateCatDto {
name: string;
age: number;
breed: string;
}
Possui apenas três propriedades básicas. Posteriormente, podemos usar o DTO recém-criado dentro do CatsController
:
// cats.controller.ts
@Post()
async create(@Body() createCatDto: CreateCatDto) {
return 'This action adds a new cat';
}
DICA Nosso
ValidationPipe
pode filtrar propriedades que não devem ser recebidas pelo manipulador de métodos. Nesse caso, podemos sanitizar as propriedades aceitáveis e qualquer propriedade não incluída na lista de permissões é automaticamente retirada do objeto resultante. No exemploCreateCatDto
, nossa lista de permissões de propriedades é aname
,age
, ebreed
. Saiba mais aqui.
Há um capítulo separado sobre como lidar com erros (ou seja, trabalhar com exceções) aqui.
Abaixo está um exemplo que utiliza vários decoradores disponíveis para criar um controlador básico. Este controlador expõe alguns métodos para acessar e manipular dados internos.
// cats.controller.ts
import { Controller, Get, Query, Post, Body, Put, Param, Delete } from '@nestjs/common';
import { CreateCatDto, UpdateCatDto, ListAllEntities } from './dto';
@Controller('cats')
export class CatsController {
@Post()
create(@Body() createCatDto: CreateCatDto) {
return 'This action adds a new cat';
}
@Get()
findAll(@Query() query: ListAllEntities) {
return `This action returns all cats (limit: ${query.limit} items)`;
}
@Get(':id')
findOne(@Param('id') id: string) {
return `This action returns a #${id} cat`;
}
@Put(':id')
update(@Param('id') id: string, @Body() updateCatDto: UpdateCatDto) {
return `This action updates a #${id} cat`;
}
@Delete(':id')
remove(@Param('id') id: string) {
return `This action removes a #${id} cat`;
}
}
DICA Nest CLI fornece um gerador (esquemático ) que gera automaticamente todo o código do boilerplate para nos ajudar a evitar fazer tudo isso e tornar a experiência do desenvolvedor muito mais simples. Leia mais sobre esse recurso aqui.
Com o controlador acima totalmente definido, Nest ainda não sabe que CatsController
existe e, como resultado, não criará uma instância dessa classe.
Os controladores sempre pertencem a um módulo, razão pela qual incluímos o controllers matriz dentro do decorador @Module()
. Como ainda não definimos outros módulos, exceto a raiz AppModule
, vamos usar isso para apresentar o CatsController
:
// app.module.ts
import { Module } from '@nestjs/common';
import { CatsController } from './cats/cats.controller';
@Module({
controllers: [CatsController],
})
export class AppModule {}
Anexamos os metadados à classe do módulo usando o decorador @Module()
e agora o Nest pode refletir facilmente quais controladores devem ser montados.
Até agora, discutimos a maneira padrão do Nest de manipular respostas. A segunda maneira de manipular a resposta é usar uma biblioteca específica e manipular suas propriedades. Para injetar um objeto de resposta específico, precisamos usar o decorador @Res()
. Para mostrar as diferenças, vamos reescrever o CatsController
para o seguinte:
import { Controller, Get, Post, Res, HttpStatus } from '@nestjs/common';
import { Response } from 'express';
@Controller('cats')
export class CatsController {
@Post()
create(@Res() res: Response) {
res.status(HttpStatus.CREATED).send();
}
@Get()
findAll(@Res() res: Response) {
res.status(HttpStatus.OK).json([]);
}
}
Embora essa abordagem funcione e, de fato, permita mais flexibilidade de alguma maneira, fornecendo controle total do objeto de resposta (manipulação de cabeçalhos, recursos específicos de bibliotecas e assim por diante), deve ser usado com cuidado. Em geral, a abordagem é muito menos clara e tem algumas desvantagens. A principal desvantagem é que seu código se torna dependente da plataforma (pois as bibliotecas subjacentes podem ter APIs diferentes no objeto de resposta) e, mais difícil de testar (você precisará mockar o objeto de resposta, etc.).
Além disso, no exemplo acima, você perde a compatibilidade com os recursos do Nest que dependem do manuseio de respostas padrão do Nest, como Interceptores e decoradores @HttpCode()
/@Header()
. Para corrigir isso, você pode definir a opção passthrough
para true
, do seguinte modo:
@Get()
findAll(@Res({ passthrough: true }) res: Response) {
res.status(HttpStatus.OK);
return [];
}
Agora você pode interagir com o objeto de resposta nativo (por exemplo, defina cookies ou cabeçalhos dependendo de determinadas condições), mas deixe o restante na estrutura.