Quando pensamos em desenvolver uma funcionalidade para uma aplicação, normalmente imaginamos uma API, um servidor e toda a infraestrutura necessária para manter esse serviço funcionando.
Mas será que precisamos de uma API tradicional para executar qualquer tipo de tarefa?
Imagine que precisamos receber um webhook, executar uma rotina agendada ou processar um arquivo enviado para um serviço de armazenamento.
É nesse tipo de cenário que o Azure Functions pode ser uma alternativa interessante.
Neste artigo, quero compartilhar um pouco da experiência utilizando Azure Functions com TypeScript, passando pela criação do projeto via CLI, estruturação, integração com Azure Blob Storage e publicação na nuvem.
O que é Azure Functions e quando utilizar?
Azure Functions é um serviço de computação serverless da Microsoft Azure, que permite executar código sem precisar gerenciar diretamente toda a infraestrutura de servidores.
Isso não significa que não existam servidores, mas que boa parte da responsabilidade de provisionamento e gerenciamento fica com a plataforma.
Uma das principais características do serviço é a execução orientada a eventos, utilizando mecanismos chamados triggers.
Alguns exemplos:
- HTTP Trigger: executa uma função ao receber uma requisição HTTP, sendo interessante para endpoints e webhooks.
- Timer Trigger: executa rotinas em horários ou intervalos definidos.
- Blob Trigger: reage à criação ou atualização de arquivos em um Blob Storage.
- Queue Trigger: processa mensagens recebidas de uma fila.
Isso permite desenvolver funcionalidades que são executadas quando determinadas condições acontecem.
Por exemplo, imagine um sistema que precisa gerar relatórios diariamente. Em vez de manter um processo próprio responsável pelo agendamento, podemos utilizar uma Function com Timer Trigger.
Outro cenário seria receber notificações de uma plataforma de pagamentos. Nesse caso, um HTTP Trigger poderia receber o webhook e iniciar o processamento da informação.
Mas isso significa que Azure Functions serve apenas para tarefas pequenas?
Não necessariamente.
Uma Function também pode executar regras de negócio mais complexas. O importante é avaliar o comportamento da carga de trabalho, o tempo de execução, os custos e as necessidades de escalabilidade.
Criando nosso projeto com Azure Functions Core Tools
Uma das coisas que achei bastante interessantes ao utilizar Azure Functions foi a facilidade de criar e gerenciar projetos por meio da CLI.
O Azure Functions Core Tools disponibiliza o comando func, que permite criar projetos, adicionar funções, executar o ambiente local e publicar aplicações no Azure.
Para começar, precisamos ter o Node.js e o Azure Functions Core Tools instalados.
Vamos criar um projeto utilizando TypeScript e o modelo de programação v4.
func init azure-functions-example \ --worker-runtime node \ --language typescript \ --model V4 Depois, acessamos a pasta:
cd azure-functions-example Agora podemos criar nossa primeira função utilizando um HTTP Trigger:
func new \ --template "HTTP trigger" \ --name UploadFile \ --authlevel function Com poucos comandos, já temos a estrutura inicial do projeto preparada.
Outro detalhe interessante é que não precisamos criar manualmente toda a configuração de uma aplicação Node.js do zero.
O Core Tools gera os arquivos necessários para trabalhar com o runtime do Azure Functions.
Entendendo a estrutura do projeto
Utilizando TypeScript e o modelo de programação v4, teremos uma estrutura inicial semelhante a esta:
azure-functions-example/ ├── src/ │ └── functions/ │ └── UploadFile.ts ├── host.json ├── local.settings.json ├── package.json └── tsconfig.json Alguns desses arquivos merecem atenção.
host.json
Contém configurações globais do runtime, permitindo ajustar comportamentos que afetam a aplicação de funções.
local.settings.json
Armazena configurações utilizadas durante o desenvolvimento local, incluindo variáveis de ambiente e conexões com serviços.
Como esse arquivo pode conter credenciais, ele não deve ser enviado para o repositório.
src/functions
É onde podemos organizar as funções responsáveis por responder aos eventos da aplicação.
No modelo v4, essa estrutura é flexível. Isso significa que também podemos separar serviços, regras de negócio e integrações em outros diretórios, conforme o projeto cresce.
Um ponto importante é que várias funções podem fazer parte de uma mesma Function App, compartilhando configurações e sendo implantadas como uma unidade.
Exemplo prático: HTTP Trigger com Azure Blob Storage
Vamos imaginar um cenário simples: precisamos receber um conteúdo por HTTP e armazená-lo no Azure Blob Storage.
O Blob Storage é um serviço de armazenamento de objetos da Azure, bastante utilizado para arquivos, documentos, imagens e outros dados não estruturados.
Nosso fluxo será o seguinte:
- Um cliente envia uma requisição HTTP.
- Nossa Azure Function recebe os dados.
- O conteúdo é enviado ao Blob Storage.
- A função retorna uma resposta indicando o resultado da operação.
Para isso, vamos utilizar o SDK oficial da Azure:
npm install @azure/storage-blob Agora podemos implementar nossa Function.
import { app, HttpRequest, HttpResponseInit, InvocationContext, } from "@azure/functions"; import { BlobServiceClient } from "@azure/storage-blob"; import { randomUUID } from "node:crypto"; export async function uploadFile( request: HttpRequest, context: InvocationContext ): Promise<HttpResponseInit> { try { const body = (await request.json()) as { content?: string; }; if (typeof body.content !== "string" || !body.content) { return { status: 400, jsonBody: { error: "O campo content é obrigatório.", }, }; } if (Buffer.byteLength(body.content, "utf8") > 1024 * 1024) { return { status: 413, jsonBody: { error: "Conteúdo acima de 1 MB.", }, }; } const connectionString = process.env.BLOB_STORAGE_CONNECTION_STRING; if (!connectionString) { throw new Error("Conexão com Blob Storage não configurada"); } const blobServiceClient = BlobServiceClient.fromConnectionString(connectionString); const containerClient = blobServiceClient.getContainerClient("uploads"); await containerClient.createIfNotExists(); const fileName = `${randomUUID()}.txt`; const blockBlobClient = containerClient.getBlockBlobClient(fileName); await blockBlobClient.uploadData( Buffer.from(body.content, "utf8"), { blobHTTPHeaders: { blobContentType: "text/plain", }, } ); return { status: 201, jsonBody: { message: "Arquivo armazenado com sucesso", fileName, }, }; } catch (error) { context.error("Erro ao armazenar arquivo", error); return { status: 500, jsonBody: { error: "Erro interno ao armazenar arquivo", }, }; } } app.http("UploadFile", { methods: ["POST"], authLevel: "function", handler: uploadFile, }); Nesse exemplo, utilizamos app.http() para registrar nossa Function e configurar o HTTP Trigger.
Definimos que ela responderá a requisições POST e utilizamos o BlobServiceClient para realizar a comunicação com o serviço de armazenamento.
O nome do arquivo é gerado automaticamente e o conteúdo recebido é enviado para um container chamado uploads.
Para simplificar o exemplo, estamos armazenando texto. Em uma implementação real de upload de imagens ou documentos, poderíamos trabalhar com arquivos binários, validação de formato e outras medidas de segurança.
Configurando o armazenamento
Para realizar essa integração, precisamos configurar a conexão com o Blob Storage.
No arquivo local.settings.json, podemos adicionar a seguinte configuração:
{ "IsEncrypted": false, "Values": { "AzureWebJobsStorage": "UseDevelopmentStorage=true", "FUNCTIONS_WORKER_RUNTIME": "node", "BLOB_STORAGE_CONNECTION_STRING": "UseDevelopmentStorage=true" } } Nesse exemplo, estamos utilizando o Azurite, que permite simular o Azure Storage durante o desenvolvimento local.
Precisamos ter o Azurite instalado e em execução para que essas configurações funcionem.
Caso utilizemos uma conta real do Azure Storage, devemos configurar a conexão correspondente.
Em produção, uma alternativa mais segura é utilizar identidades gerenciadas, evitando depender de chaves de acesso armazenadas na aplicação.
Executando localmente
Com o ambiente configurado e as dependências instaladas, podemos iniciar o projeto:
npm start O comando utiliza os scripts definidos no projeto TypeScript para compilar e iniciar o runtime local.
Nossa Function estará disponível em um endereço semelhante a:
http://localhost:7071/api/UploadFile Podemos enviar uma requisição de teste com o seguinte JSON:
{ "content": "Meu primeiro arquivo com Azure Functions!" } Se tudo estiver configurado corretamente, a Function armazenará o conteúdo no Blob Storage e retornará o nome do arquivo criado.
O nível authLevel: "function" exige uma chave ao chamar o endpoint publicado no Azure. Durante o desenvolvimento local pelo Core Tools, essa exigência normalmente não é aplicada.
Vale lembrar que, em produção, uma chave de Function não substitui uma estratégia completa de autenticação e autorização.
Publicando nossa Function no Azure
Outro ponto que achei bastante prático foi o processo de deploy por meio da própria CLI.
Depois de desenvolver e testar a função, podemos utilizar o comando:
func azure functionapp publish <NOME_DA_FUNCTION_APP> Esse comando publica o projeto em uma Function App previamente criada no Azure.
Para realizar a publicação, precisamos estar autenticados em nossa conta e ter os recursos de nuvem devidamente provisionados.
Também precisamos configurar as variáveis de ambiente utilizadas pela aplicação, já que o arquivo local.settings.json não é publicado automaticamente como configuração de produção.
A vantagem é conseguir trabalhar com criação, execução e publicação do projeto utilizando ferramentas de linha de comando, sem depender exclusivamente do portal da Azure.
Para projetos maiores, o ideal é complementar esse processo com uma pipeline de CI/CD, automatizando testes e implantações.
Azure Functions ou uma API tradicional?
Depois desse exemplo, podemos chegar a uma pergunta importante: por que utilizar uma Azure Function se poderíamos implementar a mesma funcionalidade em uma API com Express ou NestJS?
Na prática, ambas conseguem receber uma requisição HTTP e armazenar um arquivo no Blob Storage.
A diferença não está exatamente no que conseguimos implementar, mas em como queremos executar, gerenciar e escalar essa funcionalidade.
| Azure Functions | API tradicional |
|---|---|
| Execução orientada a triggers | Normalmente executada como serviço HTTP |
| Escalabilidade gerenciada conforme o plano | Escalabilidade configurada conforme a infraestrutura |
| Interessante para eventos e rotinas | Interessante para APIs com muitos recursos e fluxos |
| Pode ter cold start em determinados planos | Pode manter instâncias sempre disponíveis |
| Cobrança dependente do plano e consumo | Custos dependentes da hospedagem escolhida |
Imagine que temos um sistema de e-commerce com autenticação, catálogo de produtos, carrinho e pedidos.
Uma API com NestJS pode ser uma escolha bastante interessante para organizar esses diferentes módulos e regras de negócio.
Agora imagine que, toda vez que um pedido é finalizado, precisamos gerar um documento e armazená-lo no Blob Storage.
Essa tarefa poderia ser executada por uma Azure Function, acionada por um evento ou mensagem de fila, sem precisar ficar dentro do fluxo principal da API.
E aqui aparece um detalhe importante: não precisamos necessariamente escolher uma abordagem e abandonar a outra.
Uma aplicação pode utilizar uma API tradicional para suas operações principais e Azure Functions para determinadas integrações, tarefas assíncronas e rotinas.
Quais são os trade-offs?
Apesar das facilidades, utilizar Azure Functions também exige algumas considerações.
Uma delas é o cold start, que pode ocorrer em determinados planos quando uma instância precisa ser inicializada para executar a função. Isso pode adicionar latência à primeira requisição.
Também precisamos considerar limites e configurações de tempo de execução, que variam conforme o plano de hospedagem.
Outro ponto é a observabilidade. Mesmo trabalhando com funções relativamente pequenas, precisamos acompanhar falhas, logs e métricas, principalmente quando temos várias funções integradas a diferentes serviços.
Além disso, o modelo serverless não significa automaticamente menor custo. Para cargas constantes ou muito intensas, outras opções de hospedagem podem ser mais vantajosas.
A escolha depende das necessidades da aplicação.
Conclusão
Explorar Azure Functions com TypeScript me mostrou como podemos simplificar a implementação de determinadas funcionalidades, principalmente quando trabalhamos com eventos, integrações e rotinas.
Um dos pontos que mais gostei foi a experiência com o Azure Functions Core Tools. Utilizar o comando func para criar projetos, adicionar novas funções, executar localmente e realizar o deploy torna o desenvolvimento bastante prático.
A integração com serviços como Azure Blob Storage também abre possibilidades para construir fluxos sem precisar manter uma aplicação inteira dedicada a cada responsabilidade.
Mas acredito que o principal aprendizado seja entender onde essa abordagem faz sentido.
Azure Functions não é necessariamente uma substituição para APIs tradicionais. É mais uma alternativa arquitetural que podemos utilizar conforme o problema que precisamos resolver.
E, como em outras decisões de arquitetura de software, o mais importante é entender os benefícios, os custos e os trade-offs antes de escolher a solução.



