Skip to content

Ciclo de vida

Uma aplicação não termina quando responde HTTP. Ela também precisa drenar requisições, concluir tarefas aceitas e fechar recursos.

run() para o caso comum

ts
await app.run({ port: 4000 });

run():

  • inicia o servidor;
  • registra shutdown para SIGINT e SIGTERM;
  • imprime URLs da API, docs e health check quando disponíveis.

Use listen(port) quando outro código controlar sinais e encerramento.

Trabalho depois da resposta

Para uma notificação que não precisa atrasar o cliente:

ts
@Post("/:id/notify")
@AfterResponse()
async notify(@Param("id", Number) id: number) {
  await mailer.sendTaskReminder(id);
}

A rota responde 202 e o método continua com o mesmo contexto de requisição.

Configure limites:

ts
app.backgroundJobs({
  concurrency: 8,
  queueLimit: 100,
});

Com worker e fila ocupados, uma nova tarefa recebe 503.

Para observar rejeições por fila cheia sem depender do log HTTP:

ts
app.events.on("background.rejected", ({ requestId, route, reason }) => {
  metrics.increment("background_rejected", { route, reason });
  logger.warn({ requestId, route, reason });
});

O evento tem reason: "queue-full" e não interfere na resposta original.

Capture falhas:

ts
app.onBackgroundError((error, route) => {
  logger.error({ error, route });
});

Se esse callback de observabilidade também falhar, o scheduler continua processando tarefas. A falha do callback é registrada no logger da aplicação com o erro original e os metadados da tarefa.

Esse scheduler é em memória. Não oferece persistência, retry ou recuperação depois que o processo morre.

Health checks próprios

ts
app.healthCheck("mailer", async () => {
  return mailer.ping();
});

Ao menos um check registra dois endpoints:

  • GET /health/live confirma que o processo HTTP está vivo. Ele não consulta dependências e deve ser usado pela sonda de liveness.
  • GET /health/ready verifica todas as dependências registradas e deve ser usado pela sonda de readiness ou pelo load balancer.

Os checks de readiness são executados em paralelo. Se qualquer um falhar ou exceder o timeout, /health/ready responde 503 com status degraded.

Configure o prazo individual dos checks e quantas requisições de readiness podem executar ao mesmo tempo:

ts
app.configureHealthChecks({
  timeout: 2_000,
  maxConcurrentRequests: 8,
});

Use null em qualquer opção para remover o respectivo limite.

Registre recursos próprios

ts
app.onClose(async () => {
  await client.disconnect();
});

Providers também podem declarar descarte:

ts
app.provide("mailer", {
  useFactory: () => createMailer(),
  onDispose: (mailer) => mailer.close(),
});

Shutdown ordenado

app.close():

  1. para de aceitar novas conexões;
  2. aguarda requisições e tarefas aceitas;
  3. descarta scopes de requisição;
  4. executa hooks e fecha providers raiz.

shutdownTimeout limita a espera. Quando vence, os AbortSignal ativos são abortados e conexões restantes são fechadas.

disposalTimeout limita o descarte de providers e hooks onClose(). Se um recurso não terminar nesse prazo, app.close() rejeita em vez de permanecer bloqueado indefinidamente.

Promessas que ignoram o signal não podem ser interrompidas à força pelo JavaScript.

Cancelar I/O em timeout e shutdown

Na maioria das rotas, não é preciso fazer nada. Um caso comum é consultar a API de uma transportadora para acompanhar uma entrega. Passe context.signal ao fetch para cancelar essa chamada se a rota exceder o timeout ou a aplicação receber um sinal de encerramento:

ts
@Get("/orders/:id/tracking")
async tracking(
  @Param("id") orderId: string, 
  @Context() context: RequestScope
) {
  const response = await fetch(
    `https://api.carrier.example/orders/${orderId}/tracking`,
    {
      headers: { 
        Authorization: `Bearer ${process.env.CARRIER_TOKEN}` 
      },
      signal: context.signal,
    },
  );

  return response.json();
}

Se a operação não aceitar AbortSignal, ela pode continuar até terminar mesmo depois de o cliente receber 504. Nesse caso, o framework não consegue parar o trabalho por conta própria.

Se o shutdown exceder shutdownTimeout, app.close() rejeita e os recursos são fechados quando as requisições em andamento terminarem.

Ciclo de vida declarativo

Plugins e integrações participam do ciclo por meio do contexto declarativo:

ts
const LifecycleModule = defineModule({
  name: "app",
});

const app = await createApplication(LifecycleModule, {
  configure: (app) => {
    app.onStart(async () => {});
    app.onClose(async () => {});
  },
});

Registre o plugin no campo plugins do módulo. onStart() executa quando o servidor inicia e onClose() durante o encerramento.

Na maioria das aplicações, defineModule → createApplication → run é suficiente.

Você chegou ao fim da trilha

Você começou com uma rota e terminou com contratos validados, DI, SQL, autenticação, testes e shutdown. As páginas de referência abaixo servem para consulta, não como continuação obrigatória.

Feito para APIs que continuam simples quando crescem.