DexCode Packages
Menu

@dexcode/helpers

HttpClient e Gateway

Chama as APIs a partir do servidor Next.js. O browser fala só com o próprio app, e o app repassa a chamada para a API. Use quando a API não aceita requisições vindas do browser (CORS) ou quando a URL dela não pode ficar exposta.

Como funciona

fluxo
Componente / service
   │  http.get({ url: "/ws/01001000/json" })
   ▼
HttpClient ──► /api/gateway/viacep/ws/01001000/json      (browser → app, mesma origem)
                  │
                  ▼  src/app/api/gateway/[service]/[...path]/route.ts
               Gateway ──► https://viacep.com.br/ws/01001000/json   (servidor → servidor)
  • O HttpClient roda onde o seu código roda (normalmente no browser). Ele só conhece o nome do serviço.
  • O Gateway roda no servidor, dentro de um route handler. Ele conhece as URLs reais (variáveis de ambiente) e faz a chamada para a API.
  • O browser nunca envia nada direto para a API. Por isso a chamada não passa por CORS, e a API recebe a requisição vinda do servidor.

Estrutura no app

Ao final do passo a passo, o app terá estes arquivos:

estrutura
.env                                          URLs das APIs
src/
  config/
    gateway.ts                                cadastro dos serviços
  app/
    api/
      gateway/
        [service]/
          [...path]/
            route.ts                          rota que repassa para a API
  services/
    ViaCepService.ts                          chamadas da API usando o HttpClient

1. Instale

terminal
npm install @dexcode/helpers

2. Coloque as URLs das APIs no .env

.env
VIACEP_SERVICE_URL=https://viacep.com.br
DEXCODE_ADDRESS_SERVICE_URL=http://localhost:5000
Não use o prefixo NEXT_PUBLIC_. Essas variáveis só são lidas no servidor, e sem o prefixo a URL da API não vai para o JavaScript do browser.

3. Cadastre os serviços em src/config/gateway.ts

Cada chave de services é o nome do serviço, usado na URL do gateway e no HttpClient. O declare module no final faz o editor sugerir esses nomes (Ctrl+Espaço) e acusar erro em nomes que não existem.

src/config/gateway.ts
import { Gateway } from "@dexcode/helpers";

const services = {
  viacep: process.env.VIACEP_SERVICE_URL,
  dexcode_address_service: process.env.DEXCODE_ADDRESS_SERVICE_URL,
};

export const gateway = new Gateway({ services });

// Autocomplete do "service" no HttpClient
declare module "@dexcode/helpers" {
  interface GatewayRegistry {
    services: typeof services;
  }
}

Para adicionar uma API nova, crie a variável no .env e uma linha em services. Ela já passa a aparecer no autocomplete.

4. Crie a rota do gateway

Crie o arquivo exatamente neste caminho: src/app/api/gateway/[service]/[...path]/route.ts

src/app/api/gateway/[service]/[...path]/route.ts
import { gateway } from "@/config/gateway";

const handler = gateway.handler;

export {
  handler as GET,
  handler as POST,
  handler as PUT,
  handler as PATCH,
  handler as DELETE,
};
A pasta precisa se chamar [service], no singular. O Gateway lê o parâmetro com esse nome, então [services] ou outro nome faz toda chamada responder 404. O [...path] pega o resto do caminho, com quantos níveis tiver.

Para conferir, abra no browser ou no terminal:

terminal
curl http://localhost:3000/api/gateway/viacep/ws/01001000/json

5. Crie o service

O HttpClient recebe só o nome do serviço. Os caminhos (url) são relativos à URL cadastrada no .env.

src/services/ViaCepService.ts
import { HttpClient } from "@dexcode/helpers";

export interface Endereco {
  cep: string;
  logradouro: string;
  bairro: string;
  localidade: string;
  uf: string;
}

class ViaCepService {
  private http = new HttpClient({ service: "viacep" });

  getEnderecoByCep(cep: string) {
    return this.http.get<Endereco>({ url: `/ws/${cep}/json` });
  }
}

export const viaCepService = new ViaCepService();

6. Use no componente

src/app/cep/BuscaCep.tsx
"use client";

import { useState } from "react";
import { HttpClientError } from "@dexcode/helpers";
import { viaCepService, type Endereco } from "@/services/ViaCepService";

export function BuscaCep() {
  const [endereco, setEndereco] = useState<Endereco | null>(null);
  const [erro, setErro] = useState("");

  async function buscar(cep: string) {
    try {
      setEndereco(await viaCepService.getEnderecoByCep(cep));
      setErro("");
    } catch (error) {
      if (error instanceof HttpClientError) {
        setErro(`A API respondeu ${error.status}`);
      }
    }
  }

  return (
    <div>
      <input placeholder="CEP" onBlur={(e) => buscar(e.target.value)} />
      {endereco && <p>{endereco.logradouro} - {endereco.localidade}/{endereco.uf}</p>}
      {erro && <p>{erro}</p>}
    </div>
  );
}

Requisições

Todos os métodos (get, post, put, patch, delete) recebem as mesmas opções e retornam o body já convertido: JSON quando a API responde JSON, senão texto.

ts
const http = new HttpClient({ service: "dexcode_address_service" });

// GET com query string
const lista = await http.get<Endereco[]>({ url: "/enderecos?cidade=Recife" });

// POST com JSON (padrão)
await http.post({ url: "/enderecos", data: { cep: "01001000", numero: "10" } });

// PUT / PATCH / DELETE
await http.put({ url: "/enderecos/1", data: { numero: "20" } });
await http.patch({ url: "/enderecos/1", data: { numero: "30" } });
await http.delete({ url: "/enderecos/1" });

// Formulário
await http.post({ url: "/login", data: { usuario: "ana" }, contentType: "application/x-www-form-urlencoded" });

// Upload: FormData vai como está, o boundary é gerado automaticamente
const form = new FormData();
form.append("arquivo", file);
await http.post({ url: "/anexos", data: form });

// Headers extras e outras opções do fetch
await http.get({ url: "/privado", config: { headers: { Authorization: `Bearer ${token}` } } });

Com o retorno no formato padrão das APIs DexCode, use o DefaultResponse do @dexcode/types:

ts
import type { DefaultResponse } from "@dexcode/types";

const response = await http.get<DefaultResponse<Endereco[]>>({ url: "/enderecos" });

Erros

  • Resposta que não é 2xx: o HttpClient lança HttpClientError, com status e body (a resposta da API já convertida).
  • Serviço não cadastrado ou variável de ambiente vazia: o Gateway responde 404.
  • API fora do ar ou URL errada: o Gateway responde 502.
  • Qualquer outra resposta da API é repassada como veio: mesmo status e mesmo body.

Chamando a API direto (serverSide: false)

Por padrão toda chamada passa pelo gateway. Com serverSide: false, o browser chama a API direto, sem passar pelo servidor Next. Isso só funciona se a API aceitar CORS do seu domínio, e exige uma baseUrl pública:

ts
const http = new HttpClient({
  service: "viacep",
  baseUrl: process.env.NEXT_PUBLIC_VIACEP_SERVICE_URL,
});

await http.get({ url: "/ws/01001000/json", serverSide: false });
APIs que só aceitam chamadas do servidor vão recusar requisições com serverSide: false.

Em Server Components e server actions

O gatewayUrl padrão (/api/gateway) é relativo, e no servidor o fetch não aceita URL relativa. Se um service for chamado no servidor, informe a URL completa do app:

ts
const http = new HttpClient({
  service: "viacep",
  gatewayUrl: `${process.env.APP_URL}/api/gateway`, // ex.: http://localhost:3000
});

Nesse caso a chamada vai do servidor Next para ele mesmo e só depois para a API. Se o código roda apenas no servidor, também dá para pular essa volta e montar a URL com gateway.resolveUrl("viacep", "/ws/01001000/json").

Opções do Gateway

PropTipoPadrãoDescrição
services*Record<string, string | undefined>—Nome do serviço → URL base da API. Use variáveis de ambiente sem NEXT_PUBLIC_.
forwardedHeadersstring[]["content-type", "accept", "authorization"]Headers do browser repassados para a API. Os demais (cookies, origin...) não são enviados.

Além do handler usado no route.ts, o Gateway expõe resolveUrl(service, path), que monta a URL da API (ou retorna null se o serviço não existe), e forward(request, service, path), que faz o repasse.

Opções do HttpClient

PropTipoPadrãoDescrição
service*GatewayServiceName—Nome do serviço cadastrado no Gateway. Com o GatewayRegistry preenchido, o editor sugere os nomes.
gatewayUrlstring"/api/gateway"URL da rota do gateway. Precisa ser absoluta quando a chamada roda no servidor.
baseUrlstring—URL da API, usada só nas chamadas com serverSide: false.

Opções de cada requisição

PropTipoPadrãoDescrição
urlstring""Caminho na API, com query string se precisar. Ex.: /ws/01001000/json
dataunknown—Body. Objetos viram JSON (ou form-urlencoded); FormData e Blob vão como estão.
contentTypeContentType"application/json"Content-Type do body. Tipo exportado pelo @dexcode/types.
serverSidebooleantruetrue passa pelo gateway; false chama a API direto do browser em baseUrl.
configRequestInit—Opções extras do fetch (headers, signal, cache...). method e body são ignorados.

Segurança

O gateway repassa qualquer caminho de um serviço cadastrado para quem conseguir acessar o app. Ele resolve o CORS e esconde a URL, mas não substitui a autenticação. A API continua precisando validar o token (Authorization, que é repassado). Se a rota /api/gateway for protegida pelo createProxy, só usuários logados conseguem usá-la.