Home > Blog > Desenvolvimento Web
Desenvolvimento Web
JavaScript
Programação

TTY no Node.js: Guia Prático

Atualizado em: 11 de agosto de 2026

Terminal Linux usado em desenvolvimento e administração de aplicações Node.js

Ferramentas de linha de comando precisam se comportar de maneira diferente quando escrevem em um terminal interativo, em um arquivo ou em um pipeline. O módulo TTY no Node.js ajuda a identificar terminais, consultar dimensões, detectar suporte a cores e controlar modos de entrada usados por CLIs, prompts, menus e aplicações de terminal.

Um programa que sempre imprime cores, barras de progresso e animações pode funcionar bem na tela, mas produzir caracteres de controle indesejados quando sua saída é redirecionada. Da mesma forma, ativar modo raw sem restaurar o terminal pode deixar o teclado aparentemente quebrado depois que o processo termina.

Neste guia, você aprenderá a usar process.stdin.isTTY, process.stdout.isTTY, ReadStream, WriteStream, dimensões do terminal, cores, modo raw, eventos de resize, códigos ANSI, backpressure e práticas para criar CLIs acessíveis e seguras.

O que é uma TTY?

TTY é uma abreviação histórica de teletypewriter. Em sistemas modernos, representa uma interface de terminal. No Node.js, stdin, stdout e stderr podem ser conectados a um terminal, arquivo, pipe, socket ou outro destino.

A documentação oficial do módulo TTY descreve as classes e propriedades disponíveis. Para entrada interativa, consulte também a documentação oficial de Readline e o guia de Readline no Node.js.

Detectando um terminal

console.log({
  stdin: process.stdin.isTTY,
  stdout: process.stdout.isTTY,
  stderr: process.stderr.isTTY
});

Quando a propriedade é verdadeira, o stream está conectado a um terminal. Quando é falsa ou ausente, a entrada ou saída pode estar redirecionada.

Compare:

node cli.js
node cli.js > output.txt
cat data.txt | node cli.js

No primeiro caso, stdout costuma ser uma TTY. No segundo, é um arquivo. No terceiro, stdin recebe um pipe.

Adaptando a saída

if (process.stdout.isTTY) {
  showProgressBar();
} else {
  printMachineReadableOutput();
}

Uma CLI deve oferecer uma saída estável para automação. Barras de progresso, spinners e atualizações na mesma linha devem ser desativadas quando stdout não é interativo.

Separando dados de mensagens

Use stdout para o resultado principal e stderr para progresso ou avisos:

process.stdout.write(JSON.stringify(result));
process.stderr.write('Processamento concluído\n');

Assim, o usuário pode redirecionar somente os dados:

node report.js > report.json

O artigo de Console no Node.js detalha stdout, stderr e logs estruturados.

Importando node:tty

const tty = require('node:tty');

Normalmente, você não precisa criar streams TTY manualmente. process.stdin, process.stdout e process.stderr já são configurados pelo runtime. A criação direta exige file descriptors válidos e conhecimento do ambiente.

ReadStream e WriteStream

Quando conectados a um terminal, stdin pode ser uma instância de tty.ReadStream e stdout ou stderr podem ser instâncias de tty.WriteStream.

if (process.stdout instanceof tty.WriteStream) {
  console.log('Saída interativa');
}

Na maioria dos casos, verificar isTTY é mais simples e suficiente.

Dimensões do terminal

if (process.stdout.isTTY) {
  console.log({
    columns: process.stdout.columns,
    rows: process.stdout.rows
  });
}

As dimensões ajudam a ajustar tabelas, menus e quebras de linha. Elas podem mudar durante a execução e não devem ser consideradas permanentes.

Evento resize

if (process.stdout.isTTY) {
  process.stdout.on('resize', () => {
    redraw({
      columns: process.stdout.columns,
      rows: process.stdout.rows
    });
  });
}

Evite redesenhar em excesso durante redimensionamento contínuo. Aplique debounce quando a renderização for cara.

Calculando largura de conteúdo

string.length não representa sempre a largura visual. Emojis, caracteres combinados, ideogramas e códigos ANSI podem ocupar larguras diferentes. Para tabelas complexas, use uma biblioteca que calcule largura de terminal corretamente.

Não corte strings por índices simples quando a saída contém Unicode. Isso pode dividir pares substitutos ou sequências combinadas.

Suporte a cores

Um WriteStream TTY pode oferecer métodos relacionados a cores:

const colorDepth = process.stdout.isTTY
  ? process.stdout.getColorDepth()
  : 1;

console.log({ colorDepth });

Também é possível verificar se um número de cores é suportado:

const supports256 = process.stdout.isTTY
  && process.stdout.hasColors(256);

O resultado depende do ambiente, variáveis e terminal. Ofereça opções --color e --no-color para permitir controle explícito.

Cores ANSI

const red = '\u001b[31m';
const reset = '\u001b[0m';

if (process.stderr.isTTY) {
  process.stderr.write(`${red}Erro${reset}\n`);
} else {
  process.stderr.write('Erro\n');
}

Códigos ANSI não devem ser enviados para arquivos e sistemas que esperam texto limpo. Bibliotecas de cores costumam detectar o ambiente e simplificar o reset.

Acessibilidade e cores

Nunca use apenas cor para transmitir significado. Inclua símbolos, texto ou rótulos:

console.error('ERRO: configuração inválida');

Considere contraste, temas claros e escuros e usuários que desativam cores.

Movendo o cursor

WriteStream oferece métodos para controlar o cursor:

process.stdout.cursorTo(0);
process.stdout.clearLine(0);
process.stdout.write('Progresso: 50%');

Essas operações devem ser usadas apenas quando stdout é uma TTY. Em um arquivo, caracteres de controle podem aparecer literalmente.

Atualizando a mesma linha

function renderProgress(current, total) {
  const percent = Math.floor((current / total) * 100);

  if (!process.stdout.isTTY) {
    console.log({ current, total, percent });
    return;
  }

  process.stdout.cursorTo(0);
  process.stdout.clearLine(0);
  process.stdout.write(`Progresso: ${percent}%`);
}

No fim, escreva uma quebra de linha para restaurar a posição esperada.

Modo raw

Em modo normal, o terminal processa teclas e entrega linhas. Em modo raw, caracteres chegam imediatamente:

if (process.stdin.isTTY) {
  process.stdin.setRawMode(true);
  process.stdin.resume();
  process.stdin.setEncoding('utf8');
}

Isso permite atalhos, jogos, menus e leitura de teclas. O processo passa a ser responsável por tratar Ctrl+C e outras sequências.

Lendo teclas

process.stdin.on('data', key => {
  if (key === '\u0003') {
    cleanupAndExit();
    return;
  }

  console.log('Tecla:', JSON.stringify(key));
});

\u0003 representa Ctrl+C em muitos terminais. Teclas especiais podem chegar como sequências de vários caracteres.

Restaurando o terminal

Sempre desative modo raw no encerramento:

function restoreTerminal() {
  if (process.stdin.isTTY && process.stdin.isRaw) {
    process.stdin.setRawMode(false);
  }

  process.stdin.pause();
  process.stdout.write('\n');
}

Registre handlers para SIGINT, SIGTERM e fluxo normal. Não dependa apenas do evento exit para operações assíncronas.

Cleanup idempotente

let cleaned = false;

function cleanup() {
  if (cleaned) return;
  cleaned = true;
  restoreTerminal();
}

O mesmo cleanup pode ser chamado por vários caminhos sem repetir comandos ou causar erros.

Readline ou modo raw?

Use Readline para perguntas, histórico, autocomplete e edição de linha. Use modo raw quando precisa reagir imediatamente a teclas individuais.

  • Readline: prompts, comandos e formulários simples.
  • Raw mode: atalhos, menus navegáveis e interfaces personalizadas.

Para a maioria das CLIs, Readline é mais seguro e fácil de manter.

Entrada por pipe

Uma ferramenta deve aceitar stdin não interativo:

async function readStdin() {
  const chunks = [];

  for await (const chunk of process.stdin) {
    chunks.push(chunk);
  }

  return Buffer.concat(chunks).toString('utf8');
}

Defina limite de tamanho. Uma entrada ilimitada pode consumir toda a memória.

Saída para scripts

Ofereça formatos explícitos:

node cli.js --format=json
node cli.js --format=table

O formato table pode ser padrão em TTY e JSON em automação, mas evite mudanças surpreendentes. Documente o comportamento e permita escolha manual.

Backpressure em stdout

async function write(stream, text) {
  if (stream.write(text)) return;

  await new Promise(resolve => {
    stream.once('drain', resolve);
  });
}

Terminais costumam consumir rápido, mas pipes lentos podem causar acúmulo. O guia de Streams no Node.js explica backpressure.

TTY e testes

Testes automatizados normalmente não possuem TTY real. Separe lógica de decisão:

function chooseOutputMode({ isTTY, format }) {
  if (format) return format;
  return isTTY ? 'interactive' : 'json';
}

Teste a função com ambos os valores, sem depender do terminal do ambiente de CI.

Testes de integração

Para CLIs interativas, pseudo-terminals podem simular comportamento real. Use ferramentas próprias do sistema ou bibliotecas maduras. Cubra:

  • Ctrl+C;
  • redimensionamento;
  • cores ativadas e desativadas;
  • entrada por pipe;
  • saída redirecionada;
  • falha durante modo raw.

Terminais em containers

Executar docker run -it aloca uma TTY. Sem -t, isTTY tende a ser falso. Não exija terminal para serviços de backend.

CI e logs

Ambientes de integração contínua podem simular suporte a cores ou definir variáveis específicas. Ofereça uma configuração explícita para evitar sequências ANSI em logs armazenados.

Segurança em interfaces de terminal

Não imprima dados externos diretamente em sequências de controle. Entradas maliciosas podem conter caracteres ANSI que alteram a tela, escondem texto ou criam links enganosos.

function stripControlCharacters(value) {
  return value.replace(/[\u0000-\u001F\u007F]/g, '');
}

Uma regex simples não cobre todos os controles ANSI. Use uma biblioteca especializada para sanitização completa.

Dados sensíveis

Prompts de senha não devem ecoar caracteres. Readline padrão não resolve todos os casos. Use uma biblioteca confiável, restaure o terminal em erros e evite armazenar a senha em logs.

Erros comuns

  • Imprimir cores em arquivos: códigos ANSI poluem a saída.
  • Não restaurar raw mode: o terminal fica em estado inesperado.
  • Assumir largura fixa: tabelas quebram ao redimensionar.
  • Usar string.length como largura: Unicode é calculado incorretamente.
  • Misturar dados e progresso: pipelines recebem conteúdo inválido.
  • Ignorar backpressure: pipes lentos aumentam memória.
  • Confiar apenas em cor: acessibilidade é prejudicada.

Boas práticas para produção

  • Verifique isTTY antes de usar recursos interativos.
  • Ofereça saída estável para automação.
  • Separe stdout e stderr.
  • Permita ativar ou desativar cores.
  • Restaure modo raw em todos os caminhos.
  • Trate Ctrl+C e sinais.
  • Limite entrada por pipe.
  • Respeite backpressure.
  • Sanitize caracteres de controle externos.
  • Teste com TTY e sem TTY.

Conclusão

O TTY no Node.js permite que uma aplicação reconheça o ambiente de terminal e adapte sua experiência. Detecção de TTY, dimensões, cores, cursor e modo raw tornam possível criar CLIs interativas sem prejudicar pipelines e arquivos.

O ponto principal é oferecer dois comportamentos confiáveis: uma interface amigável para pessoas e uma saída previsível para máquinas. Com cleanup idempotente, acessibilidade, sanitização e respeito aos streams, ferramentas de terminal ficam mais robustas e fáceis de integrar.

Os 10 Melhores Cursos de Programação de 2026

Descubra os melhores cursos de programação. Aprenda a escolher o curso ideal para iniciar ou avançar na carreira de desenvolvedor

POSTS RELACIONADOS

Ver todos

Seta para a direita