Blog

  • O básico de animações para programadores (as) — ou para quem não manja nada

    O básico de animações para programadores (as) — ou para quem não manja nada

    Você tem pouca habilidade artística (assim como eu, rs) ou te falta conhecimento sobre o processo criativo de animações MAS ainda sim você deseja ingressar nesse mundo divertidíssimo? Seja bem-vindo(a) ao clube! Neste artigo conheceremos o Wick Editor, uma ferramenta poderosa, intuitiva e gratuita de animações.

  • Desvendando a Comunicação com LLMs: O Poder da Engenharia de Prompts

    Desvendando a Comunicação com LLMs: O Poder da Engenharia de Prompts

    Você já se viu em uma conversa com uma inteligência artificial que não parecia te entender muito bem? Ou pediu algo a um LLM (Large Language Model) e o resultado não era bem o que você esperava?

    Se a resposta for sim, você não está sozinho.
    A forma como interagimos com esses modelos poderosos, por meio de prompts, faz toda a diferença. Compreender o potencial dos prompts, e também suas limitações, é o segredo para se tornar muito mais produtivo e tirar o máximo proveito da inteligência artificial.

    Neste post, vamos falar sobre:

    • Como os LLMs funcionam e como seus prompts são processados.
    • Estratégias para criar prompts que realmente funcionam.
    • Como ajustar suas instruções quando o resultado inicial não atende às suas expectativas.

    Bora?

    O que é um LLM?

    Modelos de Linguagem de Larga Escala (LLMs) são um tipo de inteligência artificial treinada com uma quantidade gigantesca de dados de texto. Essa vasta quantidade de informações permite que eles compreendam e gerem linguagem de forma notavelmente parecida com a humana.

    Eles funcionam prevendo qual será a próxima palavra em uma frase, com base no contexto das palavras anteriores. Pense nisso como um “autocompletar” super inteligente! É exatamente por isso que eles conseguem gerar textos tão coerentes e contextuais.

    Três conceitos importantes para usar LLMs:

    • Contexto: É a informação que você fornece e que ajuda o modelo a entender do que você está falando. Assim como em uma boa conversa com um amigo, quanto mais contexto você oferece, mais sentido a conversa fará.
    • Tokens: O texto que você insere ou que o modelo gera é dividido em unidades menores chamadas tokens. Um token pode ser uma palavra, parte de uma palavra ou até uma única letra. O modelo processa esses tokens para gerar respostas. Usar poucos tokens pode deixar o contexto fraco; usar demais pode confundir o modelo, ultrapassar o limite máximo de processamento ou até gerar custos maiores em alguns serviços.
    • Limitações: É importante entender que LLMs não são mágicos. Eles não “entendem” como humanos. Eles apenas identificam padrões nos dados em que foram treinados. Por isso, a qualidade e a diversidade dos dados de treinamento são cruciais, e preconceitos presentes nos dados originais podem levar a vieses nas respostas do LLM. Eles podem errar, “alucinar” respostas (inventar informações) ou gerar algo sem sentido.

    O que é um prompt?

    Um prompt é essencialmente um pedido ou instrução em linguagem natural que você faz a um LLM para que ele execute uma tarefa específica. Ele é a ponte entre o que você quer e o que o modelo pode oferecer, fornecendo o contexto necessário através de tokens e ajudando o modelo a contornar suas limitações.

    Exemplo: Se você escrever “Crie uma função em JavaScript para calcular o fatorial de um número”, o modelo utilizará o conhecimento adquirido durante seu treinamento para gerar essa função.

    É importante notar que modelos diferentes (como o GPT da OpenAI, Claude da Anthropic ou Gemini do Google) podem processar prompts de maneiras distintas. Além disso, até o mesmo modelo pode gerar resultados ligeiramente variados para o mesmo prompt, devido a fatores como a arquitetura interna e os parâmetros de aleatoriedade que podem ser configurados.

    O que é engenharia de prompts?

    Pense na engenharia de prompts como a arte de se comunicar de forma eficaz com uma inteligência artificial. Imagine que você está pedindo ajuda a um amigo para fazer uma tarefa. Se quiser um resultado específico, você vai dar instruções claras e detalhadas, certo?

    Com os LLMs, a lógica é a mesma. Um prompt bem elaborado é a chave para ajudar o modelo a entregar exatamente o que você precisa. Essa habilidade de criar prompts otimizados é o que chamamos de engenharia de prompts.

    Um bom prompt é:

    • Claro e objetivo: A ambiguidade pode confundir o modelo, levando a respostas indesejadas.
    • Rico em contexto: Forneça informações suficientes para o modelo entender a profundidade da sua solicitação, mas evite exagerar nos detalhes irrelevantes.
    • Iterativo e testável: Se o resultado inicial não for o esperado, não desista! Ajuste o prompt e tente de novo. A engenharia de prompts é, em sua essência, um processo contínuo de tentativa e erro e aprimoramento.

    Exemplo: refinando prompts para ter melhores resultados

    Vamos imaginar que você está usando o GitHub Copilot e escreve:

    “Crie uma função que eleva os números de uma lista ao quadrado.”

    Essa solicitação parece direta, mas na verdade, ela deixa várias perguntas no ar, como:

    • Em qual linguagem de programação a função deve ser criada?
    • A função deve incluir números negativos?
    • E se a lista contiver outros tipos de dados além de números?
    • A função deve modificar a lista original ou criar uma nova?

    Agora, vamos refinar o prompt para torná-lo muito mais eficaz:

    “Escreva uma função em Python que receba uma lista de inteiros e retorne uma nova lista com os quadrados de cada número, excluindo os negativos.”

    Com este prompt revisado, somos claros, específicos e com restrições bem definidas. Ao fornecer mais contexto e detalhes, o Copilot conseguirá gerar uma resposta muito mais alinhada ao que você realmente precisa!

    Dica extra: Para tarefas mais complexas ou quando você precisa de um formato de saída muito específico, adicionar exemplos diretamente no prompt pode ser extremamente útil. Por exemplo, se você quer a saída em formato JSON, inclua um pequeno exemplo de como o JSON deve ser estruturado.

    💡 No fundo, engenharia de prompts é sobre boa comunicação.

    Como melhorar os resultados ao usar LLMs

    Mesmo com prompts bem escritos, às vezes o resultado inicial não é o que esperávamos. Vamos ver como lidar com os problemas mais comuns e otimizar suas interações:

    1. Prompt confuso

    Pedir “Corrija os erros deste código e otimize” pode ser muito ambíguo. O modelo deve corrigir primeiro? Otimizar o quê, desempenho ou legibilidade?

    Como resolver: Seja explícito no que você deseja e divida a tarefa em etapas claras:

    • “Corrija os erros do código fornecido.”
    • “Agora, otimize o código corrigido para melhorar seu desempenho.”
    • “Por fim, adicione testes unitários para a função principal.”

    2. Limite de tokens

    Se seu prompt for muito longo ou você pedir uma saída excessivamente extensa, o modelo pode travar, cortar a resposta ou até “alucinar” (gerar informações inventadas ou sem sentido).

    Como resolver:

    • Quebre pedidos grandes em partes menores e envie-os sequencialmente.
    • Envie apenas o contexto necessário. Em vez de mandar um arquivo inteiro, forneça somente a função ou o trecho de código relevante. Seja objetivo e vá direto ao ponto, eliminando informações irrelevantes para a tarefa.

    3. Suposições erradas

    Dizer “Adicione autenticação ao meu app” pode não funcionar se o modelo não tiver informações sobre qual framework você está usando ou como a arquitetura do seu aplicativo funciona. Ele fará suposições que podem não ser válidas para o seu contexto.

    Como resolver:

    • Especifique o que deseja com clareza e detalhes técnicos: “Adicione autenticação com JWT em um app Node.js usando Express e MongoDB.”
    • Forneça detalhes técnicos importantes, mencione boas práticas específicas e esteja preparado para iterar conforme o resultado. Lembre-se de que o modelo se beneficia quando você o orienta com restrições e formatos de saída específicos. Por exemplo: “Retorne a resposta em formato JSON” ou “A lista de itens deve ser ordenada alfabeticamente”.

    Boas práticas de engenharia de prompts

    Para maximizar a eficácia de suas interações com LLMs, adote estas práticas:

    • Forneça contexto suficiente, mas evite sobrecarregar o modelo com detalhes desnecessários.
    • Escreva de forma clara, concisa e precisa.
    • Divida tarefas complexas em partes menores e gerenciáveis.
    • Explique requisitos e restrições com total clareza.
    • Seja educado e direto: embora os LLMs não possuam sentimentos, um tom respeitoso e direto geralmente resulta em respostas mais úteis.
    • Use formatação (quando aplicável): Em alguns casos, empregar aspas, listas, marcadores ou cabeçalhos dentro do próprio prompt pode ajudar o LLM a entender melhor a estrutura das suas instruções e o que você espera.

    Próximos passos

    Hoje você deu um grande passo para melhorar sua comunicação com LLMs! Você aprendeu

    • O que são LLMs e a importância crucial do contexto.
    • O que é engenharia de prompts e como escrever prompts verdadeiramente eficazes.
    • Como evitar os erros mais comuns e ajustar suas estratégias ao usar modelos de linguagem.

    Encare cada prompt como uma oportunidade de aprendizado. Quanto mais você pratica, mais intuitiva se torna a comunicação com essas inteligências artificiais. Continue experimentando, testando e refinando, e você vai ver o impacto direto na sua produtividade.

  • Agentes de IA com LLMs de Código Aberto: Integração Prática com o Model Context Protocol (MCP)

    Agentes de IA com LLMs de Código Aberto: Integração Prática com o Model Context Protocol (MCP)

    As LLMs de código aberto continuam sendo fundamentais por diversos motivos — técnicos, estratégicos, éticos e econômicos. Elas possibilitam o ajuste fino (fine-tuning) para finalidades e domínios específicos, além de permitirem a execução em ambientes isolados, o que viabiliza o tratamento seguro de dados sensíveis ou sigilosos. Por serem mais acessíveis, também fomentam a inovação ao facilitar sua adoção por empresas e organizações de menor porte.

    No entanto, as LLMs de código aberto apresentam limitações importantes: não são atualizadas com eventos recentes, não têm acesso a dados privados e geralmente não possuem integração nativa com sistemas e softwares do mundo real. Até recentemente, integrar ferramentas a uma LLM de código aberto — como LLaMA (Meta) ou Mistral — exigia o desenvolvimento de uma camada de integração personalizada para cada modelo, com baixo potencial de reutilização.

    O acesso das LLMs aos dados privados, seja através de base de dados ou do sistema de arquivos, pode contribuir para resolução de problemas cotidianos em uma empresa, como os seguintes:

    • RH e Gestão de Pessoas: Consultar automaticamente informações de colaboradores (ex.: exames médicos vencidos, férias acumuladas) em um banco Postgres e gerar relatórios para o setor de Recursos Humanos.
    • Financeiro: Criar dashboards que buscam em tempo real dados de fluxo de caixa em um banco MongoDB, permitindo ao gestor ver projeções de saldo a partir de entradas e saídas já registradas, desde que essas projeções já constem no banco de dados.
    • Atendimento ao cliente: Consultar dados de histórico de chamados em um Postgres para que um agente de IA sugira respostas rápidas ao time de suporte.
    • Compliance e Auditoria: Um agente de IA pode percorrer diretórios internos, localizar arquivos de contratos, extrair cláusulas críticas (ex.: prazos ou multas) e consolidar tudo em um relatório automático.
    • Engenharia: Buscar documentos técnicos em PDF ou Word em pastas compartilhadas para acelerar a elaboração de propostas ou relatórios de projeto.
    • Jurídico: Localizar petições e anexos dentro do sistema de arquivos para preparar respostas processuais.

    O Model Context Protocol (MCP), um protocolo aberto introduzido pela Anthropic em 2024, busca padronizar a forma como LLMs interagem com dados e ferramentas externas, facilitando o desenvolvimento de agentes de IA mais eficientes e interoperáveis. Um agente de IA é essencialmente um sistema de software autônomo que executa fluxos de tarefas para atingir um objetivo em nome do usuário.

    Com a maturação do ecossistema de ferramentas baseadas em MCP, já é possível integrar:

    Criamos um projeto de demonstração, disponível aqui, baseado na biblioteca Fast MCP em Python. Essa biblioteca permite ao usuário criar rapidamente seu próprio servidor MCP. O projeto está configurado para disponibilizar à LLM um conjunto de ferramentas, incluindo:

    • Múltiplas ferramentas que consultam dados sobre publicações científicas por meio da API pública do OpenAlex.org, e
    • Uma ferramenta para realizar o download de uma imagem de container do Docker Hub, utilizando as credenciais fornecidas pelo usuário nos arquivos .env e .gemini/settings.json.

    O servidor pode ser acessado por qualquer cliente LLM que ofereça suporte a MCPs. No entanto, ele foi testado e validado com o Gemini CLI, cuja configuração está incluída na pasta .gemini do projeto de demonstração. Abaixo estão dois prompts de exemplo que utilizam as ferramentas integradas: uma para acessar APIs externas e outra para realizar o download de imagens do repositório Docker. Os procedimentos de instalação e configuração estão detalhados no arquivo README.md do repositório Git.

    Prompt 1 – Número de publicações do Japão:

    What is the number of publication sources in Japan?

    Prompt 2 – Distribuição dos tipos de publicações do Brasil:

    Show me the distribution of the types of publication sources in Brazil.

    Prompt 3 – Acessa DockerHub e descarrega imagem do Python:

    Use the mainAPIServer MCP to pull the python:3.9-slim image from Docker Hub and run it to display the Python version. Ensure Docker Hub authentication uses the credentials provided in .gemini/settings.json and .env.

  • Segurança Efêmera e Zero Trust: A Nova Fronteira do SSH com OTP via  Vault

    Segurança Efêmera e Zero Trust: A Nova Fronteira do SSH com OTP via Vault

    Entendendo os fundamentos: Segurança Efêmera, Zero Trust e HashiCorp Vault

    Segurança Efêmera

    Segurança Efêmera é um conceito que propõe a utilização de credenciais temporárias, com ciclo de vida curto e controlado, que só existem durante o tempo estritamente necessário para sua finalidade. Isso reduz a janela de exposição de acessos sensíveis, evitando o acúmulo de credenciais persistentes que podem ser exploradas ou comercializadas por atacantes.

    Zero Trust

    Zero Trust (“confiança zero”) é um modelo de segurança que assume que nenhuma identidade — interna ou externa — é confiável por padrão. Em vez de autorizar acessos com base na localização ou status do dispositivo, o Zero Trust exige verificação contínua, políticas explícitas de acesso mínimo necessário e monitoramento constante.

    Esse modelo é especialmente eficaz contra ataques de movimento lateral, uso indevido de credenciais privilegiadas e acessos indevidos por insiders.

    HashiCorp Vault

    O HashiCorp Vault é uma ferramenta de gerenciamento de segredos e controle de acesso que permite:

    • Distribuir credenciais efêmeras de forma centralizada;
    • Gerenciar políticas de segurança com rastreabilidade;
    • Auditar e revogar acessos em tempo real.

    Quando integrado ao SSH via One-Time Password (OTP), o Vault se torna um aliado do Zero Trust e da segurança efêmera — garantindo que cada sessão SSH só ocorra com uma autorização explícita, única e rastreável.

    O alerta real: ataque à Allianz Life

    O ataque cibernético à Allianz Life Insurance Company, detectado em 16 de julho de 2025, expôs a fragilidade do uso de credenciais estáticas em ambientes de terceiros. Criminosos exploraram acessos em uma plataforma externa integrada aos sistemas da seguradora, o que permitiu o vazamento de dados sensíveis de aproximadamente 1,4 milhão de clientes.

    Embora não tenha havido envolvimento interno direto, o incidente demonstrou como credenciais pouco protegidas e com validade prolongada podem ser reutilizadas por atacantes mesmo fora da infraestrutura principal da organização. A falta de autenticação dinâmica e visibilidade em tempo real permitiu que os acessos fossem mantidos de forma persistente e silenciosa por tempo suficiente para causar impacto significativo.

    Esse tipo de incidente evidencia um risco recorrente na segurança da informação: o uso de credenciais estáticas em sistemas privilegiados e integrados, que muitas vezes são difíceis de revogar, não são auditadas continuamente e acabam se tornando alvos fáceis no ecossistema de fornecedores e integrações.

    A solução? Substituir o modelo de confiança implícita por autenticação efêmera — com credenciais de uso único, validade curta e validadas dinamicamente. Exatamente o que Vault oferece.

    Neste artigo, mostramos como implementar a autenticação dinâmica para acesso SSH via OTP com HashiCorp Vault, fortalecendo controles, rastreabilidade e automatização como resposta a ameaças reais como essa.

    Por que autenticação efêmera faz a diferença?

    Autenticações tradicionais baseadas em chaves SSH estáticas oferecem às equipes acesso persistente — e aos atacantes oportunidades de venda, troca ou comprometimento. Já o OTP via Vault transforma o acesso em algo:

    • Efêmero e único: cada autenticação gera um token temporário.
    • Validado no Vault: sem confiança implícita, cada acesso é verificado.
    • Auditoria completa: logs detalhados por contexto, usuário e validade.
    • Revogável instantaneamente: basta alterar políticas no Vault — sem a necessidade de substituição manual das chaves nos sistemas que as precisa utilizar.

    Benefícios tangíveis do OTP via Vault + Zero Trust

    • Redução de chaves SSH persistentes.
    • Auditoria por acesso, com logs de quem fez, onde e quando.
    • Revogação imediata de acessos altos pela política no Vault.
    • Escalabilidade com integração em Terraform, Ansible e CI/CD.
    • Proteção real contra incidentes semelhantes a golpes bilionários.

    Integração prática: OTP via Vault no acesso SSH

    1.  Habilite o motor SSH no Vault

    vault secrets enable ssh
    vault write ssh/roles/otp-role \
      key_type=otp \
      default_user=vault \
      cidr_list=192.168.0.0/16
    1. Crie uma política no Vault

    # ssh-verify.hcl
    path "ssh/verify" {
      capabilities = ["create", "update"]
    }

     

    vault token create -policy="ssh-verify" -ttl=720h
    1. No servidor SSH, configure o helper de verificação de OTP

    #!/bin/bash
    export VAULT_ADDR="https://vault.seudominio.com"
    export VAULT_TOKEN="<TOKEN_COM_POLICY>"
    ROLE="otp-role"
    read -r OTP_RAW
    OTP=$(echo -n "$OTP_RAW" | tr -d '\r\n[:space:]')
    RESULT=$(vault write -address="$VAULT_ADDR" ssh/verify otp="$OTP" role="$ROLE" 2>&1)
    if [ $? -eq 0 ]; then exit 0; else exit 1; fi
    1. Integre ao PAM (Linux)

    No /etc/pam.d/sshd 

    Adicione a seguinte linha no início do script:

    auth    sufficient  pam_exec.so expose_authtok /usr/local/bin/vault-ssh-helper.sh

    Importante: verifique se o SELinux está corretamente configurado para permitir a execução do helper via PAM. O SELinux pode bloquear o script, mesmo que esteja funcional.

    Você pode ajustar as permissões com:

    sudo semanage port -m -t ssh_port_t -p tcp 443
    sudo semanage port -a -t ssh_port_t -p tcp 443 2>/dev/null || true

    Ou analisar os bloqueios no log:

    grep vault /var/log/audit/audit.log | audit2allow -M vaultpam
    semodule -i vaultpam.pp

    Reinicie o SSH:

    sudo systemctl restart sshd
    1. Geração e uso de OTP

    No cliente:

    vault write ssh/creds/otp-role ip=192.168.1.10

    Resolvido o JSON com a key → use-a como senha ao conectar via SSH:

    ssh vault@192.168.1.10
    # Cole a OTP

    Resposta ao cenário real da Allianz Life

    Se a Allianz Life e seus fornecedores estivessem utilizando autenticação SSH com OTP via Vault, o ataque teria sido muito mais difícil de executar: nenhuma credencial fixa para acesso direto a sistemas sensíveis existiria para ser reutilizada ou explorada. A autenticação efêmera por OTP protege contra:

    • Reutilização ou vazamento de credenciais — senhas estáticas mantidas em sistemas de terceiros tornam-se um ponto de falha crítico. Com OTP, as credenciais têm vida curta e uso único, tornando vazamentos inócuos.
    • Acessos persistentes e não rastreados — cada operação via SSH exigiria uma validação dinâmica com controle total pelo Vault, bloqueando movimentações não autorizadas ou persistentes.
    • Ataques laterais e movimentos furtivos — mesmo que um invasor consiga explorar um ambiente mal configurado, o tempo de acesso seria extremamente limitado, reduzindo drasticamente o potencial de dano.

    Conclusão

    Ao implementar OTP via Vault para SSH, você fortalece fortemente a segurança contra ataques de insider e credenciais comprometidas — como o golpe que atingiu o Banco Central. Com acesso efêmero, auditável e Zero Trust por design, sua infraestrutura se coloca à prova contra a próxima grande ameaça.

    Comece pequeno, em servidores de staging, e evolua para um modelo robusto que protege pessoas e sistemas — antes que alguém tente “vender” seu acesso.

  • Minimal APIs – Vinculação a formulários

    Minimal APIs – Vinculação a formulários

    Neste artigo vou apresentar alguns dos novos recursos das minimal APIs no .NET 8.

    Vamos iniciar apresentando os principais conceitos sobre as minimal APIs para quem esta começando agora.

    Introdução

    As Minimal APIs referem-se a uma abordagem mais simplificada para a criação de APIs em comparação com estruturas mais pesadas.

    As minimal APIs são constituidas dos seguintes recursos básicos:

    1. WebApplication e WebApplicationBuilder
    2. Route Handlers

    Elas são baseadas na classe WebApplication e usam a abordagem funcional para a definição de rotas e manipulação de solicitações. A estrutura básica pode ser entendida assim:

    var builder = WebApplication.CreateBuilder(args);
    
    var app = builder.Build();

    Aqui, você cria uma instância de WebApplication usando o WebApplication.CreateBuilder e, em seguida, constrói a aplicação.

    Você define os endpoints diretamente no método MapGet, MapPost, MapPut, MapDelete, etc., sem a necessidade de um controlador separado.


    app.MapGet(“/”, () => “Bem-Vindo!”);

    Você pode injetar dependências diretamente nos métodos de manipulação de solicitações.

    Por exemplo, se você precisa de um serviço, pode simplesmente incluí-lo como parâmetro no método, a seguir registre o serviço no container DI e defina o endpoint com a rota e injete o serviço no endpoint:

    var builder = WebApplication.CreateBuilder(args);builder.Services.AddSingleton(new SaudacaoService());

    var app = builder.Build();

    app.MapGet(“/”, () => “Bem-Vindo!”)

    app.MapGet(“/saudacao/{nome}”, (string nome,SaudacaoService saudacao) =>
    Results.Ok(saudacao.Saudacao(nome)));

    app.UseHttpsRedirection();
    app.Run();

    public class SaudacaoService
    {
    public string Saudacao(string nome) => $”Bem-Vindo {nome}”;
    }

    Se desejar incluir os recursos do Swagger basta incluir no projeto o pacote nuget :  Swashbuckle.AspNetCore

    E alterar o código conforme abaixo:

    var builder = WebApplication.CreateBuilder(args);builder.Services.AddEndpointsApiExplorer();
    builder.Services.AddSwaggerGen()
    ;

    builder.Services.AddSingleton(new SaudacaoService());

    var app = builder.Build();

    app.MapGet(“/”, () => “Bem-Vindo!”)

    app.MapGet(“/saudacao/{nome}”, (string nome,SaudacaoService saudacao) =>
    Results.Ok(saudacao.Saudacao(nome)));

    app.UseSwagger();
    app.UseSwaggerUI();

    app.UseHttpsRedirection();
    app.Run();

    public class SaudacaoService
    {
    public string Saudacao(string nome) => $”Bem-Vindo {nome}”;
    }

    Um recurso recém incorporado às minimal APIs é a capacidade de agrupar logicamente as rotas para um prefixo de caminho base usando RouteGroupBuilder.

    Para o exemplo acima vamos criar um novo grupo de rotas definido como ‘teste‘:

    var builder = WebApplication.CreateBuilder(args);builder.Services.AddEndpointsApiExplorer();
    builder.Services.AddSwaggerGen();

    builder.Services.AddSingleton(new SaudacaoService());

    var app = builder.Build();

    var teste = app.MapGroup(“teste”);

    teste.MapGet(“/”, () => “Bem-Vindo”);

    teste.MapGet(“/saudacao/{nome}”, (string nome,SaudacaoService saudacao) =>
    Results.Ok(saudacao.Saudacao(nome)));

    app.UseSwagger();
    app.UseSwaggerUI();

    app.UseHttpsRedirection();
    app.Run();

    public class SaudacaoService
    {
    public string Saudacao(string nome) => $”Bem-Vindo {nome}”;
    }

    O que vai produzir o seguinte resultado na interface do Swagger:

    Apresentando a vinculação a formulários

    Nas Minimal APIs já podíamos usar a interface IFormFile da mesma maneira que em aplicações mais tradicionais. Esta interface é usada para representar arquivos enviados por meio de um request HTTP multipart/form-data.

    var builder = WebApplication.CreateBuilder(args);
    var app = builder.Build();
    
    app.MapPost("/upload", (IFormFile file) =>
    {
        // Verificar se um arquivo foi realmente enviado
        if (file != null && file.Length > 0)
        {
            // Lógica para processar o arquivo aqui
            // Por exemplo, você pode salvá-lo em disco, 
            // processá-lo, etc.
            // file.CopyTo(...);
            // Ou usar um serviço para manipular o arquivo
            // fileService.ProcessFile(file);
            
            return $"Arquivo {file.FileName} recebido com sucesso!";
        }
        return "Nenhum arquivo enviado.";
    });
    
    app.Run();

    A partir do .NET 8.0 podemos vincular formulários usando o atributo [FromForm]

    Com o novo atributo [FromForm], os dados do formulário podem ser vinculados diretamente à API sem a necessidade de serialização. Isso simplifica o código e melhora o desempenho. Para usar o atributo [FromForm], basta adicioná-lo a um parâmetro de um manipulador de endpoint.

    O framework irá então vincular o valor do campo de formulário com o mesmo nome ao parâmetro.


    app.MapPost(“/formulario1”, ([FromForm] string descricao) =>
    {
    //código
    });

    Se quiser vincular vários campos de formulário, você pode listá-los como parâmetros extras marcados com o atributo [FromForm]:


    app.MapPost(“/formulario2”, ([FromForm] string nome, [FromForm] bool ativo,
    [FromForm] DateTime nascimento)
    => Results.Ok(new { nome, ativo, nascimento }));
     

    Para evitar ter que usar uma longa lista de parâmetros temos também o suporte a objetos complexos :

    app.MapPost(“/matricula”, ([FromForm] Aluno aluno)
    => Results.Ok(aluno));

    app.Run();
    public class Aluno
    {
    public string? Nome { get; set; }
    public bool Ativo{ get; set; }
    public DateTime Nascimento { get; set; }
    }

    Importante destacar que ao usar este recurso existem limitações e aspectos relacionados a segurança dos tokens anti-falsificação. (Antiforgery)

    Antiforgery

    O outro recurso disponível para as minimal APIs é o suporte aos tokens antiforgery.

    Podemos chamar o método AddAntiforgery para registrar os serviços antifalsificação e o WebApplicationBuilder adicionará automaticamente o middleware antifalsificação ao pipeline:

    var builder = WebApplication.CreateBuilder(args);builder.Services.AddEndpointsApiExplorer();
    builder.Services.AddSwaggerGen();

    builder.Services.AddSingleton(new SaudacaoService());

    builder.Services.AddAntiforgery();

    var app = builder.Build();

    App.Run();

    Exemplo de uso de tokens antiforgery, onde a interface IAntiforgery esta sendo usada para validar o request e ao final estamos desabilitando a validação :

    app.MapPost(“/aluno”, async Task<IResult> ([FromForm] Aluno aluno,HttpContext context, IAntiforgery antiforgery) =>
    {
    try
    {
    await antiforgery.ValidateRequestAsync(context);
    return Results.Ok(aluno);
    }
    catch (AntiforgeryValidationException)
    {
    return TypedResults.BadRequest(“token anti-forgery inválido”);
    }
    }).DisableAntiforgery();

    Temos assim estes novos recursos presentes agora nas minimal APIs .

    E estamos conversados…

     

  • 8 dicas rápidas de desempenho que aprendi após anos de programação

    8 dicas rápidas de desempenho que aprendi após anos de programação

    Olá a todos! Gostaria de compartilhar estas dicas de desempenho do Swift, que considero que todos deveriam conhecer, pois me custaram muito esforço e erros para aprender. Então, aqui estão oito dicas de desempenho que fizeram uma diferença real nos meus projetos. Não se esqueça de marcá-las como favoritas para referência futura.

    1) Prefira estruturas em vez de classes quando apropriado

    O Swift incentiva o uso de estruturas em vez de classes sempre que possível. As estruturas são alocadas na pilha, o que pode levar a um melhor desempenho devido a tempos de acesso mais rápidos e redução da sobrecarga de memória.

    Em vez de:

    Considere:

    Por quê?
    As estruturas fornecem semântica de cópia, o que pode evitar efeitos colaterais indesejados e tornar seu código mais seguro. Além disso, o compilador pode otimizar estruturas de forma mais eficaz do que classes, levando a ganhos de desempenho.

    Observação pessoal:
    Em um dos meus projetos, a mudança de um modelo baseado em classes para estruturas melhorou a capacidade de resposta do aplicativo, especialmente ao lidar com grandes conjuntos de dados.

    2) Aproveite as propriedades preguiçosas para inicializações que consomem muitos recursos

    As propriedades preguiçosas só são inicializadas quando acessadas pela primeira vez, tornando-as uma maneira eficaz de conservar recursos. Essa abordagem é particularmente útil para propriedades cuja inicialização é computacionalmente cara e pode não ser necessária imediatamente.

    Exemplo:

    Por quê?
    Adiar a inicialização do expensiveData até que ele seja realmente necessário reduz o tempo de carregamento inicial do aplicativo, proporcionando uma experiência mais rápida e suave ao usuário.

    Impacto no mundo real:
    A incorporação de propriedades preguiçosas em aplicativos com muitos dados reduziu significativamente os tempos de inicialização em meus projetos. Essa otimização tornou os aplicativos mais responsivos e melhorou a satisfação do usuário.

    3) Otimize o uso de matrizes pré-alocando capacidade

    Ao lidar com matrizes que crescem dinamicamente, pré-alocar sua capacidade pode melhorar significativamente o desempenho, reduzindo a sobrecarga de várias realocações de memória.

    Em vez disso:

    Faça isto:

    Por quê?
    A pré-alocação de capacidade permite que a matriz aloque memória suficiente antecipadamente para armazenar o número previsto de elementos. Isso minimiza as operações de redimensionamento e cópia, que podem ser caras, especialmente em loops críticos para o desempenho.

    Impacto no mundo real:
    Em um projeto em que preenchi grandes matrizes em loops apertados, a reserva de capacidade reduziu o tempo de processamento em quase 50%. Essa otimização não apenas melhorou a velocidade de execução, mas também aumentou a capacidade de resposta geral do aplicativo.

    4) Aproveite o poder da concorrência do Swift com Async/Await

    A sintaxe async/await do Swift revoluciona a programação concorrente, permitindo que as tarefas sejam executadas de forma assíncrona sem bloquear o thread principal. Essa abordagem moderna simplifica o código e aumenta significativamente o desempenho em operações com uso intenso de E/S.

    Exemplo:

    Por quê?
    Usar async/await garante que seu aplicativo possa lidar com solicitações de rede ou outras tarefas demoradas sem congelar a interface do usuário, resultando em uma experiência mais suave para o usuário.

    5) Minimize os opcionais em códigos críticos para o desempenho

    Os opcionais são inestimáveis para lidar com segurança com a ausência de valores, mas o uso excessivo em caminhos de código quentes pode introduzir uma sobrecarga desnecessária.

    Em vez de:

    Use:

    Por quê?
    Ao evitar opcionais desnecessários, você elimina o custo de desempacotar e verificar se há nil, levando a uma execução mais rápida.

    Dica: use opcionais apenas quando a ausência de um valor for significativa e inevitável.

    Anedota:
    A otimização de um módulo de processamento de dados em tempo real através da remoção de opcionais desnecessários reduziu o tempo de iteração em milissegundos, resultando em ganhos de desempenho cumulativos substanciais.

    6) Use tipos de valor para segurança de thread

    Os tipos de valor do Swift, como estruturas e enums, são inerentemente seguros para threads devido ao seu comportamento de “cópia na atribuição”. Isso os torna ideais para ambientes simultâneos.

    Exemplo:

    Por quê?
    Os tipos de valor evitam conflitos de dados, garantindo que cada thread trabalhe com sua própria cópia dos dados, eliminando a necessidade de mecanismos complexos de sincronização.

    Experiência:
    A mudança para tipos de valor em um aplicativo multithread simplificou o gerenciamento de concorrência e melhorou o desempenho sem comprometer a segurança.

    7) Otimize operações de string com a API de string do Swift

    A manipulação eficiente de strings é fundamental para evitar gargalos de desempenho em seu aplicativo. A API de string do Swift fornece métodos otimizados para várias tarefas.

    Em vez de:

    Use:

    Por quê?
    Usar joined(separator:) minimiza as alocações intermediárias de strings e melhora o desempenho em relação à concatenação manual em loops.

    Dica: aproveite os métodos de string integrados do Swift para simplificar e otimizar a manipulação de strings.

    Nota pessoal:
    Em um recurso de processamento de texto, mudar para joined(separator:) reduziu o tempo de processamento em mais de 30%, melhorando drasticamente a capacidade de resposta.

    8) Perfil e benchmark com o Xcode Instruments

    Nenhuma jornada de otimização está completa sem insights baseados em dados. O Xcode Instruments fornece ferramentas poderosas para identificar e resolver gargalos de desempenho.

    Como usar o Instruments:

    Inicie o Instruments: Abra o Xcode > Vá para Xcode > Abra a ferramenta de desenvolvedor > Instruments.
    Escolha um modelo: selecione um modelo de perfil (por exemplo, Time Profiler, Allocations).
    Execute seu aplicativo: inicie o perfil e execute as tarefas que deseja analisar.
    Analise os resultados: identifique pontos críticos e faça otimizações direcionadas.

    Por quê?
    O perfil garante que você se concentre em problemas reais de desempenho, em vez de suposições, levando a otimizações impactantes e eficientes.

    Anedota:
    Ao otimizar uma sequência de animação complexa, a criação de perfis revelou que uma função menor consumia tempo excessivo da CPU. Refatorá-la resultou em uma experiência de animação visivelmente mais suave.

    Conclusão

    Essas dicas de desempenho são resultado de anos de experiência prática. Cada projeto é único, portanto, adapte essas estratégias aos seus desafios específicos.

    Considerações finais:

    Teste após as alterações: faça um benchmark antes e depois das otimizações para medir o impacto.
    Mantenha-se atualizado: o Swift evolui rapidamente — mantenha-se informado sobre novos recursos e práticas recomendadas.
    Compartilhe ideias: tem suas próprias dicas? Compartilhe-as nos comentários!
    Boa programação! 🚀

    Apoie meu trabalho:

    Siga-me no Medium para mais artigos.
    Conecte-se comigo no LinkedIn.
    Explore minhas outras publicações sobre Swift e desenvolvimento de software.

  • Novidades do C# 14: melhorias no suporte a Generics com a expressão nameof

    Novidades do C# 14: melhorias no suporte a Generics com a expressão nameof

    Antes do C# 14 o uso da expressão nameof com estruturas genéricas estava restrito a declarações em que uma construção genérica possuía um tipo informado como argumento. O uso de nameof em expressões que não informassem um tipo como List<> e IEnumerable<> resultaria então em falhas.

    É o que demonstra imagem a seguir, com o uso de List<>HashSet<> e IEnumerable<> em conjunto com nameof produzindo erros na própria IDE (Visual Studio 2022):

    Clique nesta imagem para visualizar com uma melhor resolução

    Ao ativar o uso de recursos em modo Preview para a linguagem C# (no arquivo .csproj de um projeto de testes) podemos superar esta limitação, habilitando assim a funcionalidade que foi chamada de unbound generic support for nameof:

    <Project Sdk="Microsoft.NET.Sdk">
    
      <PropertyGroup>
        <OutputType>Exe</OutputType>
        <TargetFramework>net10.0</TargetFramework>
        <ImplicitUsings>enable</ImplicitUsings>
        <Nullable>enable</Nullable>
        <LangVersion>preview</LangVersion>
      </PropertyGroup>
    
    </Project>

    O código de exemplo com esses testes está na listagem seguinte:

    using System.Runtime.InteropServices;
    
    Console.WriteLine("***** Testes com C# 14 + .NET 10 | Unbound generic support for nameof *****");
    Console.WriteLine($"Versao do .NET em uso: {RuntimeInformation
        .FrameworkDescription} - Ambiente: {Environment.MachineName} - Kernel: {Environment
        .OSVersion.VersionString}");
    
    Console.WriteLine();
    Console.WriteLine($"nameof(List<object>) = {nameof(List<object>)}");
    Console.WriteLine($"nameof(HashSet<int>) = {nameof(HashSet<int>)}");
    Console.WriteLine($"nameof(IEnumerable<string>) = {nameof(IEnumerable<string>)}");
    
    Console.WriteLine();
    Console.WriteLine($"nameof(List<>) = {nameof(List<>)}");
    Console.WriteLine($"nameof(HashSet<>) = {nameof(HashSet<>)}");
    Console.WriteLine($"nameof(IEnumerable<>) = {nameof(IEnumerable<>)}");

    Na próxima imagem notamos que não foi mais apontado o erro anteriormente apresentado:

    Clique nesta imagem para visualizar com uma melhor resolução

    Ao executar esta aplicação teremos como resultado:

    Clique nesta imagem para visualizar com uma melhor resolução

    O uso de nameof com List<>HashSet<> e IEnumerable<> produziu como resultados as strings ListHashSet e IEnumerable.

    A aplicação utilizada para testes neste artigo foi disponibilizada no GitHub:

    https://github.com/renatogroffe/dotnet10-csharp14-nameof-generics

    Caso achem útil esta solução, peço por favor um ⭐️ no repositório apoiando. Fica também o convite para que vocês me sigam lá no GitHub!

  • Entendendo posicionamento com CSS de uma vez por todas

    Entendendo posicionamento com CSS de uma vez por todas

    Entendendo posicionamento com CSS de uma vez por todas

    Static, absolute, relative, fixed e sticky são mais fáceis de usar do que parece.

    Antes de começarmos, tenho uma confissão a fazer: não sou o maior fã do mundo de CSS. Na verdade, sempre tive muito mais interesse no JavaScript (apesar de enxergá-lo com olho torto por muito tempo também). No entanto, essa concepção começou a mudar quando percebi que na verdade tudo não passava de uma simples ignorância minha: eu não tinha como gostar daquilo que eu não entendia direito.

    Para tentar fazer com que você também perca esse possível desgosto por CSS, neste artigo vamos tratar de algo fundamental nesta tecnologia e que sempre causa bastante confusão: como trabalhar com o posicionamento dos elementos.

    Se você nunca saiu chutando os posicionamentos dos elementos na esperança de que a página magicamente se consertasse, você é uma pessoa fora da curva e provavelmente este artigo não é pra você, rs.

    Com esperança, ao final deste artigo você nunca mais chutará o valor do posicionamento dos elementos no seu CSS (ou ao menos fará chutes mais precisos).

    Agora sem mais delongas, vamos tratar individualmente cada um dos valores possíveis para o posicionamento de elementos no CSS. Recomendo que você os leia na ordem disponibilizada a seguir, pois as regras se complementam e entendimento pode ficar comprometido. Combinado? Então vamos lá.

    Static (estático)

    Este é o comportamento de posicionamento padrão que os seus elementos vão aderir ao serem adicionados na página. Em termos práticos isso significa que se nenhuma regra é associada de forma explícita, este valor é assumido por definição.

    static simplesmente indica aos elementos que eles devem seguir o fluxo definido declarado no documento (HTML). Ou seja, se tivermos três divs declaradas uma após a outra e elas tiverem o static como valor para posicionamento, elas simplesmente serão colocadas uma embaixo da outra na renderização.

    Como mostra o exemplo abaixo:

    Relative (relativo)

    Este tipo de posicionamento funciona de forma muito semelhante ao static, a única real diferença é que é possível configurar quatro propriedades do elemento ao qual o posicionamento está vinculado:

    1. top (cima)
    2. bottom (baixo)
    3. left (direita)
    4. right (esquerda)

    Este tipo de posicionamento foi batizado como relative pois os valores inseridos em qualquer uma destas quatro propriedades fará com que o elemento se desloque em relação a qual seria o seu posicionamento no documento com a propriedade static . O elemento é retirado do fluxo do documento enquanto os outros elementos se comportam como se ele ainda estivesse lá.

    É por isso que conseguimos sobrepor elementos usando essa propriedade, como mostra o exemplo a seguir:

    Dificilmente usaremos estas propriedades em um elemento de forma isolada, afinal, como o elemento sai do fluxo do documento, se torna muito difícil estilizar os elementos ao redor. No exemplo citado acima, teríamos que mudar as regras das outras divs também para que tudo ficasse no lugar.

    Isso daria um trabalho absurdo dependendo da página, por isso temos o posicionamento absoluto.

    Absolute (absoluto)

    Este tipo faz que o elemento designado seja totalmente retirado do fluxo do documento, ou seja, os elementos ao redor reagem como se este elemento realmente não existisse.

    Para entender o que isso significa, observe atentamente o exemplo abaixo onde atribuímos position: absolute a div-um.

    Reparou como as outras divs se moveram para onde a div-um está?

    Como o próprio nome sugere, a ideia aqui é que o elemento seja posicionado de forma absoluta em relação aos seus elementos pais (ou em outras palavras, seu container). No entanto, aqui vai a grande sacada: o elemento definido com posição absoluta só vai ser posicionado em relação a um elemento que não tenha uma posição estática. Caso não o encontre, o elemento sobe a sua árvore até encontrar um elemento que satisfaça essa condição.

    Conseguimos ver isso bem no exemplo abaixo em que o container possui posicionamento estático e o posicionamento da div-um fica absoluta em relação ao início do documento e não do container em si.

    É neste tipo de caso em que o posicionamento absoluto e o relativo conversam muito bem. Note o que acontece quando colocamos position: relative no container .

    Fixed (fixo)

    O posicionamento fixo está associado ao documento como um todo, ao contrário do posicionamento absoluto que leva em consideração um outro elemento. Ao usar o fixed em um elemento, ele terá sempre o mesmo posicionamento, mesmo levando em conta o scroll da página.

    Em outras palavras, um elemento com posicionamento fixo tem um comportamento parecido com o que vimos no exemplo do posicionamento absoluto quando não há nenhum elemento de referência. Ele sempre leva em consideração o HTML como um todo.

    Vamos ver como isso funciona na prática com o exemplo abaixo.

    Sticky (grudento)

    Este tipo é uma mistura interessante dos comportamentos obtidos com os posicionamentos fixed e relative. Enquanto o elemento não ultrapassa um limite especificado (ex: top: 10), ele possui o comportamento de elemento com posicionamento relativo; ao ultrapassar este valor limite, ele troca de comportamento e torna-se fixo até voltar ao limite especificado anteriormente.

    Isso fica mais claro com um exemplo prático. Repare na página abaixo como o a div-um se comporta. Antes de “descermos” a página, o elemento tem o comportamento de posicionamento relativo. Entretanto, a partir do momento em que o elemento alcança o top: 0, este comportamento muda: agora possui posicionamento fixo.

    Conclusão

    O CSS é uma tecnologia fantástica, mas seu potencial só é devidamente explorado quando assumimos nossa ignorância no assunto e realmente sentamos a bunda na cadeira para estudar como as coisas funcionam.

    É evidente que existem várias soluções e frameworks CSS que facilitam muito o trabalho de posicionamento de elementos e várias outras coisas — e não há dúvidas de que elas tem o seu valor — mas temos que nos lembrar que há ainda mais valor em conhecer os fundamentos do que saber usar frameworks e bibliotecas.

    Referências

  • Como Gerenciar suas Chaves SSH Automaticamente no Linux / Mac: Um Guia Prático

    Como Gerenciar suas Chaves SSH Automaticamente no Linux / Mac: Um Guia Prático

    Como Gerenciar suas Chaves SSH Automaticamente no Linux / Mac: Um Guia Prático | Quando você trabalha com múltiplos repositórios Git privados, servidores ou ambientes que requerem autenticação via SSH, talvez já tenha se deparado com o incômodo de precisar digitar a senha da chave várias vezes ou de não saber exatamente quais chaves estão carregadas. Neste post, vou te mostrar uma forma elegante de resolver isso automaticamente ao iniciar o terminal, personalizando para os nomes de arquivos de chave SSH que você usa.

    O Problema

    Por padrão, o agente SSH (ssh-agent) só carrega uma chave (geralmente ~/.ssh/id_rsa ou ~/.ssh/id_ed25519). Se você usa várias chaves, precisa rodar ssh-add <nome-do-arquivo-da-chave> cada vez que reinicia o computador ou uma nova sessão de terminal. Isso é manual, suscetível a erros e fácil de esquecer.

    A Solução

    Vamos criar um trecho de código no seu .bashrc, .zshrc (ou similar) que:

    1. Inicia o SSH agent automaticamente (caso ele ainda não esteja rodando).
    1. Carrega automaticamente todas as suas chaves SSH personalizadas, mas só se elas ainda não estiverem carregadas (evitando duplicidade).

    Vamos lá!


    1. O Código Base

    O código abaixo pode ser adicionado ao seu arquivo de configuração de shell (~/.bashrc ou ~/.zshrc). O truque é trocar <NomeSeuArquivoAqui> pelos nomes que você mesmo escolheu para suas chaves SSH.

    # SSH Agent Configuration
    
    # Start SSH agent automatically and add keys
    if [ -z "$SSH_AUTH_SOCK" ] || [ ! -S "$SSH_AUTH_SOCK" ]; then
        eval "$(ssh-agent)" > /dev/null
    fi
    
    # Function to add SSH keys if they're not already loaded
    add_ssh_keys() {
        # Get the fingerprints of currently loaded keys
        local loaded_keys=$(ssh-add -l | awk '{print $2}')
    
        # Defina aqui suas chaves SSH personalizadas
        local keys=(
            "$HOME/.ssh/<NomeSeuArquivoAqui1>"
            "$HOME/.ssh/<NomeSeuArquivoAqui2>"
            "$HOME/.ssh/<NomeSeuArquivoAqui3>"
        )
    
        # Verifique cada chave
        for key in "${keys[@]}"; do
            if [ -f "$key" ]; then
                # Obtém o fingerprint da chave
                local key_fingerprint=$(ssh-keygen -lf "$key" | awk '{print $2}')
    
                # Carrega a chave apenas se não estiver ainda carregada
                if ! echo "$loaded_keys" | grep -q "$key_fingerprint"; then
                    echo "Adicionando chave SSH: $key"
                    ssh-add "$key"
                fi
            fi
        done
    }
    

    2. Personalizando para suas chaves

    Basta trocar os nomes <NomeSeuArquivoAqui1>, <NomeSeuArquivoAqui2>, etc., pelos reais das suas chaves. Exemplo:

    local keys=(
        "$HOME/.ssh/id_trabalho"
        "$HOME/.ssh/id_pessoal"
        "$HOME/.ssh/chave_gitlab"
    )
    

    3. Chamando a função automaticamente

    Adicione no final do mesmo arquivo a chamada da função, assim ela será executada sempre ao abrir um novo terminal:

    add_ssh_keys
    

    4. Pronto para usar!

    Agora, toda vez que você abrir um terminal, seu agente SSH será iniciado (caso não esteja rodando) e suas chaves serão carregadas — de forma inteligente e sem redundância. Isso facilita o uso do Git, deploys, SSH para servidores remotos, etc.


    Dicas Adicionais

    • Adicione permissão restrita às suas chaves privadas:chmod 600 ~/.ssh/<NomeSeuArquivoAqui>
    • Se quiser adicionar novas chaves no futuro, só incluir mais linhas no array keys.
    • Funciona tanto para Bash quanto Zsh.

    Conclusão

    Esse snippet economiza seu tempo, evita esquecimentos e torna o uso do SSH bem mais prático e seguro no seu dia a dia de desenvolvedor. Basta personalizar os nomes dos arquivos das suas chaves SSH, colar o código no seu Bash/Zsh e dizer adeus ao ssh-add manual!

  • Tudo sobre o Node rodar TypeScript nativamente!

    Tudo sobre o Node rodar TypeScript nativamente!

    O Node 22 suporta nativamente TypeScript! Mas e agora? Será que é só isso? Bora aprender como você pode rodar TS muito mais fácil e quais são as principais configurações!

    Finalmente, o artigo que estou prometendo há um tempo saiu! E eu tenho muito orgulho de ser parte do time que ajudou a implementar essa funcionalidade! (mesmo apesar de não ter contribuído tanto quanto eu gostaria).

    Mas o que é essa coisa toda de Node.js rodando TypeScript?

    O Node roda TS?

    Em algumas edições anteriores, eu falei que era possível rodar TypeScript nativamente no Node usando o TSX. Historicamente, esse sempre foi o caso, porque não tínhamos como rodar nenhum tipo de arquivo além de JavaScript com o Node. E ainda não temos.

    O que acontece é que podemos utilizar loaders. Loaders são hooks especiais que permitem que a gente modifique o comportamento do module loader nativo, utilizado quando carregamos qualquer módulo ESM. Esses loaders são bastante poderosos porque eles permitem, entre outras coisas, que a gente faça ações diretamente com o código que será carregado em memória. Que é exatamente como o TSX se comporta.

    Mas agora isso não é mais necessário! A partir da versão 22.6 do Node duas novas flags experimentais foram adicionadas:

    • --experimental-strip-types: Que vai receber um arquivo TS e remover completamente qualquer anotação de tipo que exista nele. É a forma mais simples de transpilação, apenas removendo o que não é JavaScript nativo. É importante dizer que funcionalidades que requerem transformação de código como enum e namespace não vão funcionar.
    • --experimental-transform-types: implica que a flag anterior vai estar ativa, ou seja, se você passar essa flag, a anterior vai ser automaticamente passada. E permite transformação de tipos, dessa forma podemos utilizar as funcionalidades que antes não seriam habilitadas, fazendo essencialmente o suporte quase completo a TypeScript.

    O fato curioso aqui é que o nome dessa flag era para ser enable-transformation e eu sugeri que mudássemos para algo como enable-type-transformation para manter a semântica.

    Essencialmente, agora você pode fazer algo assim:

    $ node --experimental-transform-types index.ts
    

    E seu arquivo vai rodar como se você estivesse rodando com TSX usando:

    $ node --loader=tsx index.ts
    

    Node v23

    A versão 23 do Node mandou essa funcionalidade mais adiante ainda e fez com que a flag --experimental-strip-types ficasse ativa a todo o momento, ou seja, o loader nativo do TypeScript (que vamos ver mais embaixo aqui) verifica todos os arquivos, se eles forem um arquivo .ts então ele é transpilado com a remoção de tipos, o processo mais rápido.

    Isso significa que, por padrão, você pode rodar arquivos TypeScript simples usando:

    node index.ts
    

    Mas, se o arquivo tiver transformações, como enums, ele continua não funcionando e você precisa passar a flag --experimental-transform-types para o comando.

    Logo mais, provavelmente na próxima versão LTS (que deve ser a 24) essa flag deixará de ser experimental e se transformará só em --transform-types

    Mas como tudo isso funciona?

    Conheça o Amaro

    Amaro é o nome dado a um dos módulos nativos carregados pelo Node.js, ele também vem em forma de pacote do NPM, então você pode utilizá-lo separadamente do Node também. Mas esse é o coração de tudo que está acontecendo por baixo dos panos.

    O Amaro nada mais é do que um wrapper em volta do parser SWC para TypeScript em WASM, o módulo é chamado @swc/wasm-typescript e ele faz uma coisa: transpila TypeScript para JavaScript.

    Desde a versão 23, quando qualquer código é importado, basicamente existe uma checagem para garantir que a opção --experimental-strip-types está ativa, se sim, importamos o parser do amaro na memória global. Se não, retornamos apenas o código.

    É importante dizer que a transformação do código é feita de forma síncrona, então existe sim um pequeno overhead quando temos que carregar muitos arquivos.

    Tendo a melhor experiência com TS no Node

    Por mais que o Node tentasse suportar todas as configurações nativas do TypeScript, ele nunca iria suportar o tsconfig nativamente (como foi dito no artigo do Marco Ippolito), e nem faria sentido isso acontecer. Por isso, algumas configurações são necessárias para alinhar o funcionamento do TypeScript com o do Node.js.

    Você também pode ver o artigo da documentação do Node sobre essa mudança.

    Primeiro de tudo, você precisa setar o seu tsconfig para usar esnext como target e nodenext como module:

    {
      "compilerOptions": {
        "target": "esnext",
    	"module": "nodenext"
      }
    }
    

    Agora vamos ver algumas outras opções que você precisa setar para ter a melhor experiência.

    Imports de tipos precisam ser explícitos

    Quando importamos módulos que são apenas tipos, ou seja, não existe um código para ser executado ali, podemos dizer para o TypeScript não tentar resolver nenhum deles usando a keyword type:

    import type { MeuTipo, MeuOutroTipo } from 'meu-modulo'
    

    Isso fará com que tudo que esteja sendo importado dentro dos {} seja removido na transpilação, evitando processamento desnecessário. Podemos fazer isso com tipos específicos também:

    import { MinhaClasse, type MeuTipo } from 'meu-modulo'
    

    Agora só estamos importando MeuTipo como um tipo e não a classe.

    Isso parece trivial — até porque o TS vai conseguir resolver isso naturalmente quando você executar o compilador — mas para o Node, não é.

    Como o Node não tem como saber quais são os módulos que são ou não tipos, já que ele não tem type checking, você precisa colocar obrigatoriamente a anotação type. Isso pode ser configurado no seu tsconfig.json usando a opção verbatimModuleSyntax e setando para true. Então o compilador vai te avisar quando você precisar de um type.

    Seu arquivo tsconfig agora fica assim:

    {
      "compilerOptions": {
        "target": "esnext",
    	"module": "nodenext",
    	"verbatimModuleSyntax": true
      }
    }

    Imports de arquivos precisam ser explícitos

    Além de importar tipos, você também pode importar outros arquivos .ts no seu código. O Node não só suporta isso como deixa muito mais simples e, pelo menos na minha opinião, muito mais fácil de ler.

    Quando importando um arquivo local em TS, você obrigatoriamente precisa colocar a extensão .ts:

    import { MyClass } from './meu-arquivo.ts'

    Se você estiver usando a configuração padrão do Node para ESM, então você vai ter um erro no TypeScript dizendo que você não pode importar um módulo .ts exceto se allowImportingTsExtensions esteja setada como true no seu tsconfig.json. Então é isso que você precisa fazer:

    {
      "compilerOptions": {
        "target": "esnext",
    	"module": "nodenext",
    	"verbatimModuleSyntax": true,
    	"allowImportingTsExtensions": true
      }
    }
    Isso acontece porque, no ESM normal, você precisa explicitamente dizer qual é a extensão do arquivo para reduzir a quantidade de overhead no sistema de resolução de módulos em tentar descobrir que tipo de arquivo você está abrindo. E, se você tenta abrir um arquivo .ts, esse arquivo não existe, então você tem um erro de “Arquivo não existente”.

    Reescrevendo extensões

    Outra opção importante lançada com o TypeScript 5.7 e implementada pelo time do TypeScript diretamente para poder suportar o Node.js é a rewriteRelativeImportExtensions, que vai automaticamente substituir .ts por .js nos seus arquivos, permitindo que você publique o código compilado para o NPM sem precisar de nenhuma outra etapa de transpilação.

    Então, o adicionamos aqui também:

    {
      "compilerOptions": {
        "target": "esnext",
    	"module": "nodenext",
    	"verbatimModuleSyntax": true,
    	"allowImportingTsExtensions": true,
    	"reqriteRelativeImportExtensions": true
      }
    }

    No futuro

    Provavelmente, na versão 5.8, o time do TS vai incluir uma flag chamada --erasableSyntaxOnly que vai te avisar se você estiver usando tipos que não podem ser apagados (se você também estiver usando o Node sem o transform-types).

    O que isso significa para o TypeScript?

    Muita gente acha que esse é o fim do TypeScript, mas é justamente o contrário, agora o TypeScript estará mais presente do que nunca! Com todos os runtimes suportando TS nativamente, ele está gradualmente caminhando para ser, provavelmente, a linguagem padrão da web.

    O que é uma ótima oportunidade para você garantir o meu curso completo de TypeScript, a Formação TS e já se preparar para esse momento!

    Claro, ainda existem algumas mudanças que precisam ser feitas, principalmente para a configuração ser mais amigável e menos dolorosa, mas essa mudança é o início de uma pequena revolução que, talvez, substitua o JavaScript como a linguagem mais usada da Web.