Criar uma API de solicitações de suporte

CloudflareBeginner
Pratique Agora

Introdução

Um formulário de suporte precisa de uma API que diferencie uma solicitação válida de um JSON inválido, de uma rota ausente e de um serviço de tickets indisponível. Você criará essa camada HTTP com JavaScript, fará testes localmente e depois a implantará junto com um Worker upstream descartável.

Use sua própria conta de aprendizado do Cloudflare e os conhecimentos sobre autorização do dispositivo adquiridos no laboratório de conexão. Este laboratório começa em uma VM nova, com Node.js 22.22.0 e o Wrangler 4.131.1 instalado localmente no projeto em /home/labex/project/support-api. É necessário ter conhecimentos básicos sobre funções, objetos e módulos JavaScript; o comportamento HTTP e as solicitações assíncronas são explicados aqui. Os dois Workers públicos usam apenas dados sintéticos. O upstream fornecido confirma as solicitações, mas não armazena nada: ele não é um sistema de tickets persistente. O Workers Free e um subdomínio workers.dev são suficientes; não é necessário usar banco de dados, comprar um domínio ou fazer upgrade para um plano pago neste exercício. As solicitações contam para o uso do Workers na sua conta.

Você removerá os dois Workers e encerrará a sessão antes de terminar o laboratório. Mantenha o mesmo terminal aberto para preservar as variáveis do shell usadas nos nomes e URLs dos recursos.

Encaminhar solicitações por caminho e método

Nesta etapa, você atribuirá um método e uma resposta explícitos a cada URL compatível. O caminho identifica a operação; o método descreve a ação. GET /health verifica a disponibilidade, e POST /requests aceitará uma solicitação de suporte.

Entre no projeto preparado e confirme as ferramentas:

cd /home/labex/project/support-api
node --version
npx wrangler --version

A saída esperada é Node v22.22.0 e Wrangler 4.131.1. A instalação já está concluída; no seu próprio computador, instale o Node e use npm install --save-dev wrangler@4.131.1 em um projeto. Use npm ci ao reproduzir um projeto que tenha um lockfile.

Gere um nome descartável exclusivo. openssl rand -hex 6 emite 12 caracteres hexadecimais aleatórios; $(...) insere essa saída, e a atribuição do shell a salva para os comandos posteriores.

WORKER_NAME="labex-support-$(openssl rand -hex 6)"

Escreva uma configuração padrão do Wrangler. cat > file <<MARKER grava as linhas seguintes até o marcador de fechamento; como o marcador não está entre aspas, o shell substitui $WORKER_NAME.

cat > wrangler.jsonc <<CONFIG
{
  "name": "$WORKER_NAME",
  "main": "src/index.js",
  "compatibility_date": "2026-09-14",
  "workers_dev": true,
  "preview_urls": false,
  "vars": {"UPSTREAM_URL": "http://127.0.0.1:8081"}
}
CONFIG

main identifica o handler, compatibility_date seleciona o comportamento do runtime, e vars fornece um endereço upstream não secreto por meio de env. Por enquanto, ele aponta para um fixture local que será iniciado mais adiante. Os URLs públicos de preview estão desativados para manter simples o inventário de recursos.

Escreva o handler. O marcador JS entre aspas preserva o JavaScript literalmente. new URL(...).pathname extrai a rota. A expressão ternária escolhe o método permitido; a resposta HTTP 405 também anuncia esse método no cabeçalho Allow. Response.json serializa um objeto e define o tipo de conteúdo. O handler async poderá aguardar operações assíncronas nas próximas etapas.

cat > src/index.js <<'JS'
export default {
  async fetch(request, env) {
    const path = new URL(request.url).pathname;
    if (path !== '/health' && path !== '/requests') {
      return Response.json({error: 'not_found'}, {status: 404});
    }
    const allowed = path === '/health' ? 'GET' : 'POST';
    if (request.method !== allowed) {
      return Response.json({error: 'method_not_allowed'}, {
        status: 405, headers: {Allow: allowed}
      });
    }
    if (path === '/health') return Response.json({status: 'ok'});
    return Response.json({error: 'not_implemented'}, {status: 501});
  }
};
JS

Inicie o Wrangler local em segundo plano: > redireciona a saída, 2>&1 inclui os erros, e & devolve o prompt do terminal enquanto o servidor continua em execução.

npx wrangler dev --port 8080 > api.log 2>&1 &
cat api.log

Aguarde até o log informar que o servidor está pronto na porta 8080. Execute cat api.log novamente se ele ainda estiver iniciando. curl -i inclui o status e os cabeçalhos HTTP:

curl -i http://127.0.0.1:8080/health
curl -i http://127.0.0.1:8080/missing
curl -i http://127.0.0.1:8080/requests

Espere, respectivamente, 200 com {"status":"ok"}, 404 com {"error":"not_found"} e 405 com {"error":"method_not_allowed"} acompanhado de Allow: POST. Essas respostas de erro são intencionais. Use o botão de verificação enquanto o servidor ainda estiver em execução.

Analisar e validar a entrada JSON

Nesta etapa, você rejeitará entradas malformadas antes de chamar qualquer serviço upstream. HTTP 415 significa que o tipo de mídia não é compatível, 400 significa que o JSON não pôde ser analisado, e 422 significa que os dados analisados não atendem ao contrato. subject deve ser uma string contendo de 1 a 80 caracteres depois da remoção dos espaços em branco.

Substitua o handler por esta versão completa. headers.get lê o tipo de mídia declarado; dividir em ; permite um parâmetro de charset. await request.json() aguarda a análise e consome o corpo uma única vez. Um bloco try/catch converte uma exceção de análise em uma resposta previsível. O JSON também pode representar null, arrays ou números, por isso a validação verifica o formato antes de usar métodos de string. trim() normaliza o subject aceito.

cat > src/index.js <<'JS'
export default {
  async fetch(request, env) {
    const path = new URL(request.url).pathname;
    if (path !== '/health' && path !== '/requests') {
      return Response.json({error: 'not_found'}, {status: 404});
    }
    const allowed = path === '/health' ? 'GET' : 'POST';
    if (request.method !== allowed) {
      return Response.json({error: 'method_not_allowed'}, {
        status: 405, headers: {Allow: allowed}
      });
    }
    if (path === '/health') return Response.json({status: 'ok'});
    const mediaType = (request.headers.get('content-type') || '').split(';')[0].trim().toLowerCase();
    if (mediaType !== 'application/json') {
      return Response.json({error: 'unsupported_media_type'}, {status: 415});
    }
    let body;
    try {
      body = await request.json();
    } catch {
      return Response.json({error: 'invalid_json'}, {status: 400});
    }
    if (!body || Array.isArray(body) || typeof body.subject !== 'string' ||
        body.subject.trim().length < 1 || body.subject.trim().length > 80) {
      return Response.json({error: 'invalid_subject'}, {status: 422});
    }
    const subject = body.subject.trim();
    return Response.json({subject}, {status: 201});
  }
};
JS

O Wrangler recarrega o código quando a fonte é alterada. Verifique cat api.log em busca de erros de compilação. Envie uma solicitação válida: -H fornece um cabeçalho, e --data fornece o corpo e seleciona POST. As aspas simples preservam as aspas duplas do JSON no shell.

curl -i http://127.0.0.1:8080/requests -H "Content-Type: application/json" --data '{"subject":"  Printer offline  "}'

Espere 201 e {"subject":"Printer offline"}. Este é um reconhecimento mantido na memória, não um ticket salvo. Exercite três caminhos diferentes de rejeição:

curl -i http://127.0.0.1:8080/requests -H "Content-Type: application/json" --data '{'
curl -i http://127.0.0.1:8080/requests -H "Content-Type: application/json" --data '{"subject":"   "}'
curl -i http://127.0.0.1:8080/requests -H "Content-Type: text/plain" --data 'hello'

Espere 400 invalid_json, 422 invalid_subject e 415 unsupported_media_type. Teste também o JSON null, [] e {"subject":5}; cada um deve retornar 422 em vez de causar uma exceção. Use o botão de verificação; ele verifica esses limites e preserva o comportamento de health check e roteamento.

Chamar um upstream e conter seus erros

Nesta etapa, você conectará a API a um simulador de serviço de tickets fornecido. Um upstream é uma dependência chamada pelo seu serviço. O simulador retorna um ticket sintético para subjects normais e HTTP 503 para o subject especial simulate-outage; ele nunca armazena as solicitações.

Inspecione o código-fonte fornecido para entender o fixture e depois configure uma identidade exclusiva para o Worker:

cat upstream/index.js
cat > upstream/wrangler.jsonc <<CONFIG
{
  "name": "${WORKER_NAME}-upstream",
  "main": "index.js",
  "compatibility_date": "2026-09-14",
  "workers_dev": true,
  "preview_urls": false
}
CONFIG

--config seleciona esta segunda configuração. Use a porta 8081 e uma porta de inspeção separada para que os dois Workers locais possam ser executados juntos:

npx wrangler dev --config upstream/wrangler.jsonc --port 8081 --inspector-port 9230 > upstream.log 2>&1 &
cat upstream.log
curl -i http://127.0.0.1:8081/health

Aguarde o servidor ficar pronto e espere 200 com {"service":"support-upstream","status":"ok"}. Agora substitua o handler principal pela integração completa. A função global fetch faz uma solicitação de saída; JSON.stringify codifica o subject validado. await aguarda a resposta. Erros HTTP não geram exceções, portanto upstream.ok verifica explicitamente o status; catch trata separadamente uma conexão malsucedida ou uma resposta JSON que não possa ser lida. HTTP 502 informa ao cliente que a dependência falhou sem expor o corpo da resposta interna.

cat > src/index.js <<'JS'
export default {
  async fetch(request, env) {
    const path = new URL(request.url).pathname;
    if (path !== '/health' && path !== '/requests') {
      return Response.json({error: 'not_found'}, {status: 404});
    }
    const allowed = path === '/health' ? 'GET' : 'POST';
    if (request.method !== allowed) {
      return Response.json({error: 'method_not_allowed'}, {
        status: 405, headers: {Allow: allowed}
      });
    }
    if (path === '/health') return Response.json({status: 'ok'});
    const mediaType = (request.headers.get('content-type') || '').split(';')[0].trim().toLowerCase();
    if (mediaType !== 'application/json') {
      return Response.json({error: 'unsupported_media_type'}, {status: 415});
    }
    let body;
    try {
      body = await request.json();
    } catch {
      return Response.json({error: 'invalid_json'}, {status: 400});
    }
    if (!body || Array.isArray(body) || typeof body.subject !== 'string' ||
        body.subject.trim().length < 1 || body.subject.trim().length > 80) {
      return Response.json({error: 'invalid_subject'}, {status: 422});
    }
    const subject = body.subject.trim();
    try {
      const upstream = await fetch(`${env.UPSTREAM_URL}/tickets`, {
        method: 'POST',
        headers: {'Content-Type': 'application/json'},
        body: JSON.stringify({subject})
      });
      if (!upstream.ok) {
        return Response.json({error: 'upstream_unavailable'}, {status: 502});
      }
      const ticket = await upstream.json();
      return Response.json({ticket: ticket.ticket, subject}, {status: 201});
    } catch {
      return Response.json({error: 'upstream_unavailable'}, {status: 502});
    }
  }
};
JS
curl -i http://127.0.0.1:8080/requests -H "Content-Type: application/json" --data '{"subject":"Printer offline"}'
curl -i http://127.0.0.1:8080/requests -H "Content-Type: application/json" --data '{"subject":"simulate-outage"}'

Espere 201 com {"ticket":"demo-1001","subject":"Printer offline"} e depois 502 com {"error":"upstream_unavailable"}. O diagnóstico interno do simulador não deve aparecer. O subject é sintético, e este endpoint não produz efeitos persistentes. Use o botão de verificação com os dois servidores locais em execução.

Este laboratório usa HTTP comum para praticar uma fronteira com um serviço externo. Um laboratório posterior ensina service bindings para chamadas internas entre Workers. Timeouts limitados e diagnósticos mais detalhados são ensinados em Diagnose Worker Failures. O fixture retorna respostas pequenas e limitadas; uma API de produção também deve limitar o tamanho das solicitações e respostas não confiáveis.

Implantar e testar a API pública

Nesta etapa, você implantará os dois Workers na mesma conta de aprendizado e substituirá o endereço upstream local pelo URL público correspondente. Primeiro, pare os dois processos locais. Consulte jobs e use o número real de cada processo; os exemplos pressupõem que a API é 1 e o upstream é 2.

jobs
kill %1 %2

Autorize esta VM nova. A concessão identifica sua conta e permite implantar e excluir Workers. A permissão final corresponde à concessão da lição de implantação, embora este laboratório não exija um fluxo de logs.

npx wrangler login --device --browser=false --scopes account:read user:read workers_scripts:write workers_tail:read

Abra o link exibido no navegador, informe o código atual do dispositivo, revise as permissões do Wrangler, incluindo o Background Access obrigatório, selecione apenas sua conta de aprendizado e autorize. Volte ao terminal e aguarde a conclusão.

npx wrangler whoami --json

Confirme loggedIn: true, o nome da conta e o ID real em accounts. Substitua YOUR_ACCOUNT_ID abaixo por esse ID. Mantenha os nomes gerados na etapa 1; se uma variável tiver sido perdida, leia a configuração salva e restaure-a em vez de gerar outro nome de recurso.

cat > upstream/wrangler.jsonc <<CONFIG
{
  "name": "${WORKER_NAME}-upstream",
  "main": "index.js",
  "compatibility_date": "2026-09-14",
  "workers_dev": true,
  "preview_urls": false,
  "account_id": "YOUR_ACCOUNT_ID"
}
CONFIG
npx wrangler deploy --config upstream/wrangler.jsonc

Copie o URL exato do workers.dev exibido na saída da implantação. Reutilize o subdomínio existente da conta. Se o Wrangler oferecer o registro de subdomínio pela primeira vez, escolha um nome disponível e siga a confirmação; não altere um subdomínio já existente na conta.

Agora reescreva a configuração principal, substituindo os dois placeholders pelo ID da sua conta e pelo URL do upstream, sem uma barra final. global_fetch_strictly_public faz com que o fetch() de saída use o roteamento público da Internet, incluindo o outro Worker no subdomínio workers.dev desta conta. Sem essa opção, esta chamada HTTP na mesma zona pode falhar mesmo que os dois Workers funcionem de forma independente. Essa flag pertence à configuração da API implantada; o fixture local de loopback anterior não precisa dela. Consulte a orientação sobre a Fetch API.

cat > wrangler.jsonc <<CONFIG
{
  "name": "$WORKER_NAME",
  "main": "src/index.js",
  "compatibility_date": "2026-09-14",
  "compatibility_flags": ["global_fetch_strictly_public"],
  "workers_dev": true,
  "preview_urls": false,
  "account_id": "YOUR_ACCOUNT_ID",
  "vars": {"UPSTREAM_URL": "YOUR_UPSTREAM_URL"}
}
CONFIG
cat wrangler.jsonc
npx wrangler deploy

Copie o URL da API principal exibido na saída da implantação para a variável abaixo:

API_URL="https://YOUR_API.YOUR_SUBDOMAIN.workers.dev"
curl -i "$API_URL/health"
curl -i "$API_URL/requests" -H "Content-Type: application/json" --data '{"subject":"Printer offline"}'
curl -i "$API_URL/requests" -H "Content-Type: application/json" --data '{"subject":"simulate-outage"}'
curl -i "$API_URL/requests" -H "Content-Type: application/json" --data '{'

Espere os mesmos contratos observados localmente: health check 200, ticket sintético 201, erro do upstream 502 e JSON malformado 400. Aguarde a propagação do hostname antes de tentar novamente em caso de erros de conectividade. No Dashboard, selecione a mesma conta de aprendizado e abra Compute → Workers & Pages. Encontre os dois nomes exatos e compare seus endereços com a saída da implantação. Este é um checkpoint somente leitura; não crie aplicações duplicadas nessa página.

O exemplo abaixo mostra a API principal e o serviço -upstream correspondente. Na barra lateral esquerda, expanda Compute e escolha Workers & Pages. Use Search applications se sua conta tiver outros projetos. Compare os nomes completos gerados e os endereços exibidos abaixo deles com as duas saídas da implantação; seu sufixo aleatório e o subdomínio da conta serão diferentes dos deste exemplo.

Workers and Pages mostrando a API de suporte e seu Worker upstream correspondente

Os dois recursos devem aparecer na mesma conta selecionada. A presença deles confirma onde foram implantados; as respostas HTTP acima confirmam se a API funciona. Se algum nome estiver ausente, verifique o seletor de conta e a saída da implantação antes de tentar novamente. Não use Create application para duplicar uma implantação feita pela CLI.

Use o botão de verificação. Ele verifica de forma independente a propriedade dos dois Workers, o binding do upstream implantado e as respostas públicas positivas e negativas. Ele envia apenas solicitações sintéticas e sem estado ao simulador deste laboratório.

Remover os dois Workers descartáveis

Nesta etapa, você removerá a API e o upstream enquanto a autorização ainda está disponível para verificar o resultado. Estes são os únicos recursos de nuvem criados por este laboratório. Inspecione as duas configurações antes da exclusão:

cat wrangler.jsonc
cat upstream/wrangler.jsonc

Confirme o nome principal labex-support-... e o sufixo correspondente -upstream, usando o mesmo ID da conta de aprendizado. Exclua primeiro a API principal e depois o upstream. Em cada prompt, confira o nome exato e pressione a única tecla y.

npx wrangler delete
npx wrangler delete --config upstream/wrangler.jsonc

O Wrangler 4.131.1 pode remover um Worker e depois exibir um erro de autenticação ao verificar dados KV legados do Workers Sites, porque esta concessão não tem acesso a KV. Esse diagnóstico específico não comprova o sucesso nem a falha da exclusão. Não conceda permissões adicionais apenas para silenciá-lo. Atualize Workers & Pages e use o botão de verificação: um inventário autorizado bem-sucedido deve confirmar que os dois nomes estão ausentes. Erros de rede ou de autorização são inconclusivos; resolva-os antes de continuar. Preserve as outras aplicações, sua conta de aprendizado e o subdomínio dela.

Desconectar a VM

Nesta etapa, você removerá a autorização do Wrangler nesta VM depois que a verificação da limpeza dos dois recursos for aprovada. Encerrar a sessão não exclui Workers, por isso a limpeza foi feita primeiro.

npx wrangler logout
npx wrangler whoami --json

Espere "loggedIn": false explicitamente. Um comando de status sem autenticação pode terminar com código diferente de zero; isso é esperado quando o resultado estruturado informa claramente que a sessão foi encerrada. Um erro de rede não é equivalente. Use o botão de verificação e depois encerre o ambiente LabEx. O login do navegador e a conta de aprendizado continuam disponíveis para laboratórios posteriores; cada VM nova solicitará sua própria autorização.

Resumo

Você criou uma API HTTP orientada por método, analisou e validou JSON, normalizou os dados aceitos e traduziu uma falha do upstream em um erro público previsível. Você testou solicitações normais e rejeitadas localmente e no Cloudflare, verificou a propriedade das duas implantações, removeu os recursos descartáveis e desconectou a VM.

Para referência, consulte as APIs oficiais Request API, Response API e Fetch API.