Extra B: Consumo de APIs com fetch e axios
Esta página não é uma aula da grade. É material complementar, para ler depois
da aula 13 — quando o cliente web já lê a
API com fetch e aparece a pergunta que quase todo tutorial provoca: por que
não axios?
A resposta não é uma preferência. fetch e axios fazem a mesma coisa — enviam
uma requisição HTTP e entregam a resposta — e divergem em pontos precisos: o
que cada um considera falha, o que cada um faz sozinho com o corpo, como cada
um desiste de esperar, e como cada um se comporta dentro do Next.js. Esta
página percorre esses pontos com os dois clientes lado a lado, contra a mesma
API da aula 10, e termina
com um critério de escolha.
Pré-requisito de leitura: a aula 13, em especial a Parte 3 (os três
desfechos de uma chamada), e as Partes 6 e 7 da
Extra A (falha, tempo limite,
AbortSignal e dado externo sem tipo).
Nada aqui é pré-requisito de nenhuma aula, e nenhuma aula passa a usar axios.
O cliente web da disciplina continua com fetch; a Parte 9 explica por quê e
em que situação a escolha seria outra.
Objetivos
Ao final deste material, você deve ser capaz de:
- Prever, para uma resposta 2xx, uma resposta 4xx ou 5xx e a ausência de resposta, se a chamada realiza ou rejeita em cada cliente, e implementar o tratamento correspondente.
- Implementar leitura e escrita autenticada contra a API da aula 10 com os dois clientes, explicando o que cada um faz sozinho com o corpo e os cabeçalhos.
- Diagnosticar a partir do sintoma o
Content-Typeesquecido, o 404 tratado no lugar errado e a leitura de JSON de uma resposta 204. - Impor tempo limite e cancelar uma requisição nos dois clientes, e distinguir cancelar de apenas desistir de esperar.
- Construir um cliente configurado sobre axios e o equivalente sobre
fetch, com a mesma interface, sem que a biblioteca vaze para quem o usa. - Explicar por que o parâmetro de tipo do axios e o
assobreresposta.json()não conferem a resposta, e aplicar um guarda de tipo na fronteira. - Comparar o comportamento de
fetche axios num componente de servidor do Next.js quanto a memoização, cache e renderização estática. - Avaliar o custo de acrescentar uma dependência e justificar a escolha entre os dois clientes num projeto.
Conteúdo
O que já está resolvido e o que não está
A aula 13 deixou o acesso à API num módulo, lib/livros.ts, com duas funções
e uma regra: fetch só rejeita quando não há resposta nenhuma. Esta página
parte daí e responde às perguntas que o módulo não precisou responder:
| Pergunta | Onde |
|---|---|
| Como se lê a resposta, e por que em duas etapas? | Parte 2 |
| Onde vai parar o 404 em cada cliente? | Parte 3 |
| Por que a API diz que o título está faltando, se ele está no corpo? | Parte 4 |
| Como se desiste de uma chamada lenta — e o servidor fica sabendo? | Parte 5 |
| Onde se põe o token uma vez só, em vez de em cada chamada? | Parte 6 |
O que muda no Next.js quando a chamada não é fetch? | Parte 7 |
| Quanto custa uma dependência, além do tamanho? | Parte 8 |
| Afinal, qual usar? | Parte 9 |
Os arquivos completos do projeto extra-b-fetch-axios são executáveis;
veja a preparação na seção Laboratórios. Os trechos desta exposição são
somente leitura e foram extraídos desses arquivos, às vezes sem os
imports. Diferente da Extra A, aqui os exemplos precisam de rede: todos
consomem a API da aula 10 em http://localhost:3000, com o banco populado pelo
seed. As saídas mostradas são as reais; os id e os tempos variam na sua
máquina.
Parte 1 — Dois clientes para o mesmo protocolo
fetch é uma API da plataforma, definida pelo padrão Fetch do WHATWG. Existe
em todo navegador atual e, no Node.js 22, é global e estável — não se instala
nada. Retorna uma Promise<Response>. É a função que o Next.js estende com
opções próprias de cache (aula 13, Parte 4).
axios é uma biblioteca, instalada com npm install axios. Oferece uma
interface própria sobre um mecanismo de transporte que ela chama de
adapter: no navegador, XMLHttpRequest; no Node.js, o módulo http; e,
por opção, o próprio fetch. Retorna uma Promise de um objeto de resposta
dela, com o corpo já interpretado.
As duas interfaces descrevem a mesma troca de mensagens. A tabela mostra onde cada parte da requisição e da resposta aparece em cada uma:
| Parte da mensagem HTTP | fetch | axios |
|---|---|---|
| método | method: "POST" | axios.post(...) ou method: "post" |
| URL e query string | texto montado; URLSearchParams | url + params (objeto) |
| cabeçalhos da requisição | headers | headers |
| corpo da requisição | body, já serializado | data, objeto serializado pela biblioteca |
| status da resposta | resposta.status, resposta.ok | resposta.status |
| cabeçalhos da resposta | resposta.headers.get("content-type") | resposta.headers["content-type"] |
| corpo da resposta | await resposta.json(), lido à parte | resposta.data, já interpretado |
Nenhuma das duas colunas tem algo que o protocolo não tenha. A diferença está
em quem faz cada passo: no fetch, o seu código; no axios, a biblioteca,
segundo uma configuração que você pode alterar.
Parte 2 — Ler: a resposta em duas etapas
A mesma leitura — os livros de um autor — com os dois clientes. Primeiro o
fetch:
async function comFetch(autor: string): Promise<Livro[]> {
// URLSearchParams codifica o valor: espaço, acento e "&" não quebram a URL.
const consulta = new URLSearchParams({ autor, tamanho: '100' });
// Etapa 1: a promise realiza quando chegam o status e os cabeçalhos.
const resposta = await fetch(`${API_URL}/livros?${consulta}`);
console.log('fetch status:', resposta.status, resposta.statusText);
console.log('fetch content-type:', resposta.headers.get('content-type'));
console.log('fetch corpo já lido?', resposta.bodyUsed);
if (!resposta.ok) {
throw new Error(`GET /livros respondeu ${resposta.status}`);
}
// Etapa 2: o corpo é lido e interpretado à parte, e só pode ser lido uma vez.
const pagina: Pagina<Livro> = await resposta.json();
console.log('fetch corpo já lido?', resposta.bodyUsed);
return pagina.dados;
}
Depois o axios:
async function comAxios(autor: string): Promise<Livro[]> {
const resposta = await axios.get<Pagina<Livro>>(`${API_URL}/livros`, {
// O axios monta e codifica a query string a partir do objeto.
params: { autor, tamanho: 100 },
});
console.log('axios status:', resposta.status, resposta.statusText);
console.log('axios content-type:', resposta.headers['content-type']);
console.log('axios URL enviada:', axios.getUri(resposta.config));
return resposta.data.dados;
}
fetch status: 200 OK
fetch content-type: application/json; charset=utf-8
fetch corpo já lido? false
fetch corpo já lido? true
fetch → Dom Casmurro | Memórias Póstumas de Brás Cubas
axios status: 200 OK
axios content-type: application/json; charset=utf-8
axios URL enviada: http://localhost:3000/livros?autor=machado&tamanho=100
axios → Dom Casmurro | Memórias Póstumas de Brás Cubas
Por que duas etapas no fetch. A resposta HTTP chega em ordem: primeiro a
linha de status e os cabeçalhos, depois o corpo, que pode ser grande e chegar
aos pedaços. O fetch entrega a primeira parte assim que ela chega e deixa a
decisão sobre o corpo para você: ler como JSON, como texto, como binário, aos
pedaços — ou não ler, quando o status já basta. É por isso que o teste de
resposta.ok pode vir antes de gastar tempo com o corpo. O custo é
sintático: dois await para uma leitura.
Por que uma etapa no axios. A biblioteca lê o corpo inteiro e o interpreta
segundo o Content-Type da resposta antes de realizar a promise. Para a API da
disciplina, que sempre responde JSON, isso é exatamente o que se quer.
Duas consequências práticas:
- O corpo do
fetché consumido uma vez só.bodyUsedpassa atruedepois do.json(); um segundo.json()na mesma resposta rejeita comTypeError: Body is unusable: Body has already been read. Quem precisa do corpo em dois lugares guarda o resultado da primeira leitura ou lê uma cópia, feita antes comresposta.clone(). - A query string é responsabilidade de alguém. Concatenar
`?autor=${autor}`à mão funciona até o primeiro autor com&ou#no nome.URLSearchParams, nofetch, eparams, no axios, fazem a codificação.
Parte 3 — Os três desfechos, em cada cliente
A aula 13 separou três desfechos de uma chamada: resposta com dados, resposta fora da faixa 2xx e nenhuma resposta. Os dois clientes concordam sobre o terceiro e discordam sobre o segundo. O exemplo 02 faz quatro chamadas com cada um e registra se a promise realizou ou rejeitou:
async function comFetch(url: string): Promise<string> {
try {
const resposta = await fetch(url);
// Chegou aqui: houve resposta, qualquer que seja o status.
return `realizou · status ${resposta.status} · ok = ${resposta.ok}`;
} catch (erro) {
// Só chega aqui quando não houve resposta nenhuma.
const causa = erro instanceof Error && erro.cause instanceof Error ? erro.cause : undefined;
const codigo = causa && 'code' in causa ? String(causa.code) : '?';
return `rejeitou · ${erro instanceof Error ? erro.message : erro} (${codigo})`;
}
}
async function comAxios(url: string): Promise<string> {
try {
const resposta = await axios.get(url);
return `realizou · status ${resposta.status}`;
} catch (erro) {
if (!isAxiosError(erro)) throw erro;
// `response` existe quando a API respondeu fora da faixa 2xx;
// falta quando não houve resposta.
return erro.response
? `rejeitou · ${erro.code} · response.status ${erro.response.status}`
: `rejeitou · ${erro.code} · sem response`;
}
}
livro existente
fetch : realizou · status 200 · ok = true
axios : realizou · status 200
livro inexistente
fetch : realizou · status 404 · ok = false
axios : rejeitou · ERR_BAD_REQUEST · response.status 404
id fora do formato
fetch : realizou · status 400 · ok = false
axios : rejeitou · ERR_BAD_REQUEST · response.status 400
API fora do ar
fetch : rejeitou · fetch failed (ECONNREFUSED)
axios : rejeitou · ECONNREFUSED · sem response
Resumindo em tabela:
| Desfecho | fetch | axios |
|---|---|---|
| resposta 2xx | realiza; ok verdadeiro | realiza |
| resposta 4xx ou 5xx | realiza; ok falso | rejeita com AxiosError; erro.response preenchido; code ERR_BAD_REQUEST (4xx) ou ERR_BAD_RESPONSE (5xx) |
| nenhuma resposta | rejeita com TypeError; o código do sistema em erro.cause | rejeita com AxiosError; erro.response ausente; o código em erro.code |
Nenhuma das duas regras é a certa. São duas respostas diferentes à mesma pergunta — o que é falha? — e cada uma empurra o tratamento para um lugar:
- No
fetch, o status é examinado depois doawait, junto do caminho feliz. O 404 da aula 13 foi tratado assim:if (resposta.status === 404) return undefined. O risco é esquecer o teste e ler o corpo de erro como se fosse o livro. - No axios, o status fora da faixa 2xx vai para o
catch, junto da falha de rede. O risco é o oposto: tratar como falha o que o contrato prevê como resposta — o livro que não existe — e distinguir os dois casos comerro.response.
O axios permite mudar a regra com validateStatus, uma função que recebe o
status e diz se ele conta como sucesso:
const resposta = await axios.get(`${API_URL}/livros/999999`, {
validateStatus: (status) => status < 500,
});
axios com validateStatus (< 500): realizou · status 404
corpo de erro da API: {
status: 404,
titulo: 'NOT_FOUND',
detalhes: [ 'Livro 999999 não encontrado' ],
caminho: '/livros/999999',
momento: '2026-09-24T20:23:16.411Z'
}
O corpo é o envelope de erro da aula 6,
produzido pelo FiltroDeExcecoes. Ele chega nos dois clientes — em
await resposta.json() no fetch, em erro.response.data ou em
resposta.data no axios — e é ele que permite mostrar ao usuário a mensagem
que a API escreveu, em vez de um status.
Tratar o 404 de GET /livros/:id como "não existe" e o 400 de /livros/abc
como falha é decisão do domínio: o contrato da biblioteca diz que o livro
pode não existir. Examinar o status em todo desfecho, e decidir em um lugar
só o que é resposta prevista e o que é falha, é decisão do padrão. No seu
domínio, a pergunta equivalente é: quais status o seu contrato lista como
resposta normal de cada rota?
Parte 4 — Escrever: corpo, formato e token
As rotas de escrita da aula 10 pedem três coisas: um corpo JSON válido segundo
o CriarLivroDto, o cabeçalho que declara esse formato e um token de
ADMINISTRADOR. O login é o primeiro exemplo de escrita — um POST com
corpo:
async function loginComFetch(conta: { email: string; senha: string }): Promise<string> {
const resposta = await fetch(`${API_URL}/auth/login`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(conta),
});
if (!resposta.ok) throw new Error(`login respondeu ${resposta.status}`);
const { accessToken } = (await resposta.json()) as { accessToken: string };
return accessToken;
}
No fetch, body recebe o corpo já serializado — texto, bytes, um
formulário —, e o Content-Type é declarado à parte. As duas linhas andam
juntas, e é fácil escrever uma e esquecer a outra. O exemplo 03 faz isso de
propósito, entre outras tentativas:
// 1. Corpo em texto, sem declarar o formato: o fetch envia text/plain e a
// API não interpreta o corpo como JSON.
await postarComFetch('sem Content-Type', {
headers: { Authorization: `Bearer ${tokenAdmin}` },
body: JSON.stringify(livro),
});
// 2. Corpo correto, sem token.
await postarComFetch('sem token', {
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(livro),
});
--- fetch
fetch sem Content-Type → 400 · 9 detalhe(s), o primeiro: "titulo must be shorter than or equal to 200 characters"
fetch sem token → 401 · 1 detalhe(s), o primeiro: "Unauthorized"
fetch token de LEITOR → 403 · 1 detalhe(s), o primeiro: "Forbidden resource"
fetch token de ADMINISTRADOR → 201 · id 20
fetch DELETE → 204 · content-length null
fetch .json() num 204 → SyntaxError: Unexpected end of JSON input
Três linhas pedem leitura atenta.
O 400 com nove mensagens. O corpo estava lá, completo. Sem
Content-Type, o fetch envia texto com o tipo text/plain;charset=UTF-8 —
é o que a especificação manda para um body do tipo string. O NestJS só
interpreta como JSON o corpo declarado como JSON; o texto não é lido, o DTO
chega vazio, e o ValidationPipe da aula 6 reclama de cada campo. O sintoma
aponta para os dados; a causa está no cabeçalho.
A ordem entre 401 e 400. Com token de administrador e sem Content-Type,
a resposta foi 400. Sem token, seria 401 mesmo com o corpo errado: na API da
aula 10, os guards rodam antes dos pipes, e quem não passa pela
autenticação não chega à validação. O laboratório 2 pede para prever esse
caso.
O 204 sem corpo. DELETE /livros/:id responde 204 No Content. Chamar
.json() sobre um corpo vazio lança SyntaxError: não há JSON para
interpretar. Quem escreve uma função genérica sobre fetch precisa tratar o
204 antes de ler o corpo (a Parte 6 faz isso).
O mesmo roteiro com axios:
const { data: sessao } = await axios.post<{ accessToken: string }>(
`${API_URL}/auth/login`,
ADMINISTRADOR, // objeto: o axios serializa e declara application/json
);
const autorizacao = { headers: { Authorization: `Bearer ${sessao.accessToken}` } };
const post = await axios.post<Livro>(`${API_URL}/livros`, livro, autorizacao);
console.log('axios POST →', post.status, '· id', post.data.id);
console.log('axios Content-Type enviado:', post.config.headers['Content-Type']);
const patch = await axios.patch<Livro>(`${API_URL}/livros/${post.data.id}`, { ano: 1892 }, autorizacao);
console.log('axios PATCH →', patch.status, '· ano', patch.data.ano);
const del = await axios.delete(`${API_URL}/livros/${post.data.id}`, autorizacao);
console.log('axios DELETE →', del.status, '· data =', JSON.stringify(del.data));
--- axios
axios POST → 201 · id 21
axios Content-Type enviado: application/json
axios PATCH → 200 · ano 1892
axios DELETE → 204 · data = ""
Recebendo um objeto como corpo, o axios serializa e declara application/json
sozinho, e numa resposta 204 entrega data como texto vazio, sem lançar. São
os dois pontos em que a biblioteca de fato poupa código — e os dois defeitos
que o fetch deixa a cargo de quem o usa.
| Tarefa | fetch | axios |
|---|---|---|
| serializar o corpo | JSON.stringify(corpo) | automático, para objeto |
| declarar o formato | "Content-Type": "application/json" | automático, para objeto |
| enviar o token | Authorization: Bearer … em headers | idem, ou uma vez só num interceptor (Parte 6) |
| ler resposta sem corpo (204) | não chamar .json() | data vem vazio |
Parte 5 — Tempo limite e cancelamento
Nenhum dos dois clientes impõe, por padrão, um tempo limite curto a uma
chamada: o fetch não tem opção de tempo, e o timeout do axios vale 0,
isto é, nenhum. Uma API que não responde deixa a página esperando.
A API da aula 10 não tem rota lenta, então o projeto de exemplos traz um
servidor mínimo, src/servidor-lento.ts, que responde depois do atraso pedido
na URL e, principalmente, registra os pedidos que o cliente abandonou.
Com ele no ar (npm run servidor-lento, em outro terminal), o exemplo 04 faz
cinco experimentos:
// 2. fetch com AbortSignal.timeout: desiste e fecha a conexão.
inicio = performance.now();
try {
await fetch(`${LENTO}/ficha?atraso=2000`, { signal: AbortSignal.timeout(500) });
} catch (erro) {
console.log(`${ms(inicio)} fetch + timeout(500) → ${descrever(erro)}`);
}
// 3. axios com a opção `timeout`.
inicio = performance.now();
try {
await axios.get(`${LENTO}/ficha`, { params: { atraso: 2000 }, timeout: 500 });
} catch (erro) {
if (!isAxiosError(erro)) throw erro;
console.log(`${ms(inicio)} axios timeout: 500 → ${erro.code} · ${erro.message}`);
}
// 4. Cancelamento por decisão do programa (o usuário saiu da tela, digitou
// outra letra): o mesmo AbortController serve aos dois clientes.
const controle = new AbortController();
setTimeout(() => controle.abort(), 300);
inicio = performance.now();
const resultados = await Promise.allSettled([
fetch(`${LENTO}/ficha?atraso=2000`, { signal: controle.signal }),
axios.get(`${LENTO}/ficha`, { params: { atraso: 2000 }, signal: controle.signal }),
]);
1517 ms fetch sem limite → 200
503 ms fetch + timeout(500) → TimeoutError: The operation was aborted due to timeout
511 ms axios timeout: 500 → ECONNABORTED · timeout of 500ms exceeded
302 ms fetch abort() → AbortError: This operation was aborted
302 ms axios abort() → isCancel = true · ERR_CANCELED
501 ms Promise.race(500) → Error: tempo esgotado (o pedido segue aberto)
E o que o servidor viu, no outro terminal:
recebido GET /ficha?atraso=1500 · content-type: (nenhum)
respondido GET /ficha?atraso=1500 · 1502 ms
recebido GET /ficha?atraso=2000 · content-type: (nenhum)
ABANDONADO GET /ficha?atraso=2000 · o cliente desistiu aos 501 ms
recebido GET /ficha?atraso=2000 · content-type: (nenhum)
ABANDONADO GET /ficha?atraso=2000 · o cliente desistiu aos 500 ms
recebido GET /ficha?atraso=2000 · content-type: (nenhum)
recebido GET /ficha?atraso=2000 · content-type: (nenhum)
ABANDONADO GET /ficha?atraso=2000 · o cliente desistiu aos 300 ms
ABANDONADO GET /ficha?atraso=2000 · o cliente desistiu aos 298 ms
recebido GET /ficha?atraso=1000 · content-type: (nenhum)
respondido GET /ficha?atraso=1000 · 1001 ms
A última linha de cada registro é o ponto da seção. Com AbortSignal — seja
pelo timeout do fetch, pela opção timeout do axios ou por abort() —,
o cliente fecha a conexão, e o servidor fica sabendo: o pedido aparece
como abandonado. Com Promise.race, o padrão da Parte 6 da Extra A, o
programa desiste de esperar, mas o pedido segue aberto até o fim e a
resposta chega para ninguém. É a distinção da Extra A entre resolver o
problema de quem espera e o de quem trabalha, agora observada do outro lado da
rede.
| Situação | fetch | axios | Rejeita com |
|---|---|---|---|
| tempo limite | signal: AbortSignal.timeout(ms) | timeout: ms | TimeoutError · AxiosError com code ECONNABORTED |
| cancelamento pelo programa | signal: controle.signal + controle.abort() | a mesma opção signal | AbortError · CanceledError, reconhecido por isCancel e code ERR_CANCELED |
| as duas coisas juntas | AbortSignal.any([controle.signal, AbortSignal.timeout(ms)]) | signal + timeout | o que ocorrer primeiro |
Duas cautelas.
Cancelar não desfaz. O servidor lento parou o próprio temporizador porque
foi escrito para isso. Um POST que já chegou à API e foi gravado continua
gravado depois do abort(); o cliente só deixa de saber o resultado. Tempo
limite em operação de escrita pede cuidado com a repetição.
Cancelar é o que a busca pelo navegador da aula 13 precisa. A Parte 9
daquela aula resolveu a resposta atrasada com uma variável ignorar na limpeza
do Effect: a resposta velha chega e é descartada. Com um AbortController
criado no Effect e abortado na limpeza, a requisição velha nem termina. O
laboratório 3 pede exatamente isso, fora do React.
Parte 6 — Um cliente configurado
Até aqui cada chamada repetiu o endereço da API, o token e o tratamento de
erro. Num projeto, essa repetição vira um módulo — a aula 13 já começou com
lib/livros.ts. A pergunta desta parte é o que esse módulo precisa ter, e
quanto disso cada biblioteca oferece pronto.
O contrato comum vem primeiro. Quem usa o cliente recebe o corpo interpretado ou um erro do próprio projeto, e não sabe qual biblioteca está por baixo:
export class ErroDaApi extends Error {
override name = 'ErroDaApi';
constructor(
// null quando não houve resposta: API fora do ar, tempo esgotado.
readonly status: number | null,
// As mensagens do envelope de erro da API (aula 6), ou uma só, local.
readonly detalhes: string[],
opcoes?: ErrorOptions,
) {
super(status === null ? `sem resposta: ${detalhes[0]}` : `${status}: ${detalhes.join('; ')}`, opcoes);
}
}
export interface ClienteDaApi {
get<T>(caminho: string): Promise<T>;
post<T>(caminho: string, corpo: unknown): Promise<T>;
patch<T>(caminho: string, corpo: unknown): Promise<T>;
delete(caminho: string): Promise<void>;
}
Sobre axios: instância e interceptors
axios.create produz uma instância com configuração própria — endereço base,
tempo limite — sem alterar a instância padrão. Interceptors são funções que
a instância executa antes de cada envio e depois de cada resposta:
export function criarClienteAxios(baseURL = API_URL): ClienteDaApi {
// Configuração comum a todas as chamadas desta instância. A instância
// padrão (`axios.get`, usada nos exemplos 01 a 04) não é alterada.
const api = axios.create({ baseURL, timeout: 5000 });
// Interceptor de requisição: roda antes de cada envio.
api.interceptors.request.use((config) => {
if (sessao.token) {
config.headers.Authorization = `Bearer ${sessao.token}`;
}
return config;
});
// Interceptor de resposta: o primeiro callback recebe as respostas 2xx; o
// segundo, as rejeições. Aqui toda falha vira ErroDaApi.
api.interceptors.response.use(
(resposta) => resposta,
(erro: unknown) => {
if (!isAxiosError(erro)) return Promise.reject(erro);
if (!erro.response) {
return Promise.reject(new ErroDaApi(null, [erro.code ?? erro.message], { cause: erro }));
}
const corpo: unknown = erro.response.data;
const detalhes = ehCorpoDeErro(corpo) ? corpo.detalhes : [erro.message];
return Promise.reject(new ErroDaApi(erro.response.status, detalhes, { cause: erro }));
},
);
return {
get: async <T>(caminho: string) => (await api.get<T>(caminho)).data,
post: async <T>(caminho: string, corpo: unknown) => (await api.post<T>(caminho, corpo)).data,
patch: async <T>(caminho: string, corpo: unknown) => (await api.patch<T>(caminho, corpo)).data,
delete: async (caminho: string) => {
await api.delete(caminho);
},
};
}
O objeto devolvido no fim é o que isola a biblioteca: quem recebe um
ClienteDaApi não vê AxiosResponse, AxiosError nem .data. Se o projeto
trocar de biblioteca, muda este arquivo e nenhum outro.
Sobre fetch: uma função
O equivalente com fetch é uma função que faz, em sequência, o que os
interceptors e as opções do axios fazem por configuração:
async function requisitar<T>(
baseURL: string,
metodo: string,
caminho: string,
corpo?: unknown,
): Promise<T> {
// O papel do interceptor de requisição.
const cabecalhos = new Headers();
if (sessao.token) {
cabecalhos.set('Authorization', `Bearer ${sessao.token}`);
}
// O que o axios faz sozinho ao receber um objeto: serializar e declarar.
if (corpo !== undefined) {
cabecalhos.set('Content-Type', 'application/json');
}
let resposta: Response;
try {
resposta = await fetch(`${baseURL}${caminho}`, {
method: metodo,
headers: cabecalhos,
body: corpo === undefined ? undefined : JSON.stringify(corpo),
// O equivalente à opção `timeout` do axios.
signal: AbortSignal.timeout(TEMPO_LIMITE_MS),
});
} catch (erro) {
// Sem resposta: rede, porta errada, tempo esgotado.
// No Node, a falha de rede chega como TypeError com o código na causa.
const causa = erro instanceof Error ? erro.cause : undefined;
const motivo =
causa instanceof Error && 'code' in causa
? String(causa.code)
: erro instanceof Error
? erro.name
: String(erro);
throw new ErroDaApi(null, [motivo], { cause: erro });
}
// O papel do interceptor de resposta — e o que o axios decide por
// `validateStatus`: fora da faixa 2xx é falha.
if (!resposta.ok) {
const conteudo: unknown = await resposta.json().catch(() => null);
const detalhes = ehCorpoDeErro(conteudo) ? conteudo.detalhes : [resposta.statusText];
throw new ErroDaApi(resposta.status, detalhes);
}
// 204 não tem corpo; ler JSON dele lançaria SyntaxError (exemplo 03).
if (resposta.status === 204) {
return undefined as T;
}
return (await resposta.json()) as T;
}
O exemplo 05 roda o mesmo roteiro — ler, errar de três maneiras, cadastrar, repetir o ISBN, alterar e remover — contra os dois clientes:
--- cliente sobre axios
GET /livros → 3 livros
GET /livros/999999 → ErroDaApi 404 · Livro 999999 não encontrado
POST /livros sem token → ErroDaApi 401 · Unauthorized
POST /livros como LEITOR → ErroDaApi 403 · Forbidden resource
POST /livros como ADMIN → Quincas Borba (1891)
POST repetido (mesmo ISBN) → ErroDaApi 409 · ISBN 9788535910667 já cadastrado
PATCH ano → ano 1892
DELETE → removido
--- cliente sobre fetch
GET /livros → 3 livros
GET /livros/999999 → ErroDaApi 404 · Livro 999999 não encontrado
POST /livros sem token → ErroDaApi 401 · Unauthorized
POST /livros como LEITOR → ErroDaApi 403 · Forbidden resource
POST /livros como ADMIN → Quincas Borba (1891)
POST repetido (mesmo ISBN) → ErroDaApi 409 · ISBN 9788535910667 já cadastrado
PATCH ano → ano 1892
DELETE → removido
--- API fora do ar
axios GET /livros → ErroDaApi sem status · ECONNREFUSED
fetch GET /livros → ErroDaApi sem status · ECONNREFUSED
As duas rodadas são idênticas. Sem contar comentários, o cliente sobre axios
tem cerca de 30 linhas; o cliente sobre fetch, cerca de 50. A diferença é
real e pequena, e está concentrada em três pontos: serializar e declarar o
corpo, tratar o 204, e o try/catch que separa "sem resposta" de "resposta
de erro".
Encapsular o acesso à API atrás de uma interface própria, com um tipo de erro próprio, é decisão do padrão — vale para qualquer domínio e qualquer biblioteca. Quais mensagens do envelope mostrar ao usuário, e o que fazer com o 409 do ISBN repetido, é decisão do domínio. No seu projeto, a pergunta equivalente é: quais erros do seu contrato o usuário consegue corrigir sozinho, e quais só o operador do sistema resolve?
O tipo da resposta é uma promessa, nos dois
axios.get<Livro>() parece mais seguro do que await resposta.json(), que
devolve any. Não é: o parâmetro de tipo do axios é uma conversão, como o
as. Nenhum dos dois confere o que chegou. O exemplo 06 declara Livro[]
para uma rota que devolve outro formato, /livros/resumo:
// Erro proposital: /livros/resumo devolve outro formato (id, titulo,
// autor.nome e _count), mas o código declara Livro[].
const { data: comAxios } = await axios.get<Livro[]>(`${API_URL}/livros/resumo`);
const comFetch = (await (await fetch(`${API_URL}/livros/resumo`)).json()) as Livro[];
axios: TypeError: Cannot read properties of undefined (reading 'length')
fetch: TypeError: Cannot read properties of undefined (reading 'length')
com guarda: TypeError: resposta fora do contrato de Livro[] — a falha aparece na chamada que errou
com guarda: 3 livros conferidos em GET /livros
As duas primeiras linhas compilaram sem aviso e falharam na execução, no
ponto em que o código leu isbn — longe da chamada que errou. As duas últimas
usam um guarda de tipo, como o ehLivro da Parte 7 da Extra A, aplicado a um
data declarado como unknown: a falha aparece na fronteira, com uma
mensagem que diz o que aconteceu. A biblioteca não muda essa regra.
Parte 7 — No Next.js, fetch não é só um cliente
Fora do Next, as Partes 2 a 6 esgotam a comparação. Dentro dele, há uma
diferença que não aparece em nenhum exemplo de Node: o Next.js estende o
fetch global no servidor. Duas consequências vêm daí, e nenhuma vale para
uma chamada feita por outro caminho:
- Memoização. Chamadas
GETdefetchcom a mesma URL e as mesmas opções, feitas durante uma mesma renderização no servidor, são executadas uma vez só, e o resultado é compartilhado entre layout, página egenerateMetadata. - Controle de cache por chamada. As opções
cacheenext.revalidateda aula 13 (Parte 4) existem porque o Next as lê nofetch. Uma chamada que não passa por ele não tem onde declará-las.
O axios, no servidor, usa por padrão o módulo http do Node — que o Next não
observa. As afirmações abaixo foram medidas numa cópia do projeto
aula-13-dados-api (Next.js 16.3.4), contando as requisições que chegavam à
API. Os arquivos do experimento estão em next/, no projeto de exemplos, e o
laboratório 5 refaz as medições.
Memoização. A página do livro ganhou um generateMetadata que usa o mesmo
livro para o título da aba:
// O título da aba usa o mesmo livro que a página: duas chamadas iguais na
// mesma renderização.
export async function generateMetadata({ params }: PageProps<"/livros/[id]">): Promise<Metadata> {
const { id } = await params;
const livro = /^[1-9]\d*$/.test(id) ? await buscarLivro(Number(id)) : undefined;
return { title: livro?.titulo ?? "Livro" };
}
Com buscarLivro na versão com axios:
export async function buscarLivro(id: number): Promise<Livro | undefined> {
// O 404 é resposta prevista no contrato, como na versão com fetch: com
// validateStatus, ele deixa de rejeitar e chega aqui para ser examinado.
const resposta = await axios.get<Livro>(`${API_URL}/livros/${id}`, {
validateStatus: (status) => status === 404 || (status >= 200 && status < 300),
});
return resposta.status === 404 ? undefined : resposta.data;
}
buscarLivro de | Requisições à API por visita a /livros/3 |
|---|---|
lib/livros.ts (fetch, aula 13) | 1 |
lib/livros-axios.ts | 2 |
lib/livros-axios-cache.ts (axios + cache do React) | 1 |
A terceira linha é a correção. cache, do React, memoiza uma função durante
uma renderização no servidor — é o que a documentação do Next recomenda para
dados que não vêm do fetch, como uma consulta a banco:
import { cache } from "react";
import { buscarLivro as buscar } from "./livros-axios";
export const buscarLivro = cache(buscar);
Estático ou dinâmico. A listagem /livros, sem o await connection()
da aula 13, foi compilada com rm -rf .next && npm run build em quatro
versões:
| A listagem usa | Símbolo de /livros no build |
|---|---|
fetch(url) | ○ — chamada uma vez no build |
fetch(url, { cache: "no-store" }) | ƒ — a cada requisição |
axios.get(url) | ○ — chamada uma vez no build |
axios.get(url) + export const revalidate = 60 na página | ○ com revalidação de 1 min |
Sem opção, os dois se comportam igual: a página é gerada no build, com os
dados daquele momento — o "acervo congelado" da aula 13. A diferença está em
onde se declara o contrário. Com fetch, na própria chamada. Com axios,
só na rota: await connection(), export const revalidate ou export const dynamic.
O axios aceita adapter: "fetch", e com ele a chamada passa pelo fetch do
Next — a memoização volta a funcionar (1 requisição, no mesmo experimento).
Mas as opções de cache não fazem parte da configuração do axios, e o atalho
de passá-las por fetchOptions: { cache: "no-store" } quebra o build:
o Next sinaliza "rota dinâmica" lançando um erro interno, o axios o captura e
o devolve como AxiosError, e a pré-renderização falha com
Dynamic server usage: Route /livros couldn't be rendered statically.
Misturar os dois mecanismos troca uma limitação conhecida por um defeito
difícil de diagnosticar.
No navegador — a busca da aula 13, feita num componente de cliente — nada
disso se aplica: o Next não estende o fetch do navegador, e os dois
clientes voltam a ser equivalentes, com as diferenças das Partes 2 a 6.
Parte 8 — O custo de uma dependência
fetch já está no Node e no navegador. axios é código de terceiros que entra
no projeto, e o custo não se mede só em linhas poupadas.
A árvore instalada. npm install axios@1.20.0 declara quatro dependências
diretas (follow-redirects, form-data, proxy-from-env e
https-proxy-agent) e, com as delas, instala 27 pacotes, cerca de 4 MB em
node_modules. Parte dessa árvore existe para recursos que a API da
disciplina não usa — envio de formulário multipart e proxy no Node. No
navegador, o que pesa é o tamanho do código enviado ao cliente, que o bundler
reduz; no servidor e no build, pesa a árvore inteira.
A confiança. Cada pacote da árvore é código que roda na sua máquina durante
a instalação e na aplicação depois dela. Em 31 de março de 2026, a conta de
um mantenedor do axios foi usada para publicar duas versões adulteradas,
1.14.1 e 0.30.4, que ficaram disponíveis por cerca de três horas antes de
serem removidas do registro. O código do axios não mudou: as versões
acrescentavam ao package.json uma dependência nova, plain-crypto-js, cujo
script postinstall instalava um programa de acesso remoto na máquina de
quem rodasse npm install. O relato completo está no post mortem publicado
pelos mantenedores (ver Referências).
A lição não é "axios é inseguro" — a biblioteca reagiu, e o mesmo pode acontecer com qualquer pacote. É sobre como declarar dependências:
| Prática | O que teria evitado |
|---|---|
versão exata no package.json ("axios": "1.20.0", sem ^) | npm install em projeto novo ou sem lockfile resolver ^1.14.0 para 1.14.1 naquela madrugada |
package-lock.json versionado | a versão mudar entre a sua máquina, a do colega e o servidor |
npm ci em vez de npm install fora da sua máquina | a instalação ignorar o lockfile e resolver versões novas |
conferir o que mudou antes de atualizar (npm outdated, notas da versão) | a atualização entrar sem decisão |
O projeto de exemplos desta página fixa axios em 1.20.0 pelo mesmo motivo,
e a aula 6 já exige, para cada pacote novo,
uma linha de justificativa na tabela de dependências. A pergunta daquela
tabela é a desta parte: o que este pacote faz que o projeto não faria
sozinho em poucas linhas?
Parte 9 — Como escolher
As partes anteriores reduzem a escolha a poucas perguntas. Em ordem:
- A chamada acontece num componente de servidor do Next.js? Então
fetch: é por ele que passam a memoização e o controle de cache por chamada (Parte 7). Usar axios ali é possível, mas obriga a declarar na rota e a memoizar à mão o que ofetchjá faz. - O projeto já tem um cliente configurado? Então esse cliente, qualquer que seja a biblioteca por baixo. Duas bibliotecas HTTP no mesmo projeto significam dois lugares para o token, dois tipos de erro e duas regras de falha.
- O que a biblioteca faria que o projeto precisa? Se a resposta é
"serializar JSON e pôr o token", é uma função de 50 linhas (Parte 6). Se é
progresso de upload, proxy corporativo no Node, ou código que precisa
rodar num ambiente sem
fetch, a biblioteca começa a se pagar — e entra com versão fixa (Parte 8).
| Critério | fetch | axios |
|---|---|---|
| instalação | nenhuma | 27 pacotes, versão a fixar |
| status fora da faixa 2xx | realiza; examinar ok | rejeita; validateStatus muda a regra |
| corpo JSON | JSON.stringify + Content-Type | automático para objeto |
| resposta 204 | não chamar .json() | data vazio |
| tempo limite | AbortSignal.timeout | timeout |
| cancelamento | AbortController | AbortController |
| configuração comum | uma função | instância e interceptors |
| tipo da resposta | any; conferir com guarda | parâmetro de tipo, sem conferência; conferir com guarda |
| componente de servidor do Next | memoização e cache por chamada | nem uma nem outro; declarar na rota e usar cache do React |
A escolha da disciplina. O cliente web usa fetch: toda leitura do
Módulo 3 acontece primeiro no servidor do Next (aula 13, Parte 10), onde ele é
o caminho previsto; o que o axios acrescenta cabe no módulo lib/ que o
projeto já tem; e uma dependência a menos é uma decisão a menos. Nada disso
proíbe axios num projeto em que as perguntas acima tenham outra resposta.
O que se transporta desta página para qualquer domínio é o padrão: um
módulo só fala com a API, com uma interface do projeto e um tipo de erro do
projeto, e o resto do código não sabe qual biblioteca está por baixo. Com esse
módulo, trocar fetch por axios — ou o contrário — é mudar um arquivo. Sem
ele, é procurar axios. ou fetch( no projeto inteiro. No seu domínio, a
pergunta equivalente é: quantos arquivos precisariam mudar se a URL, o token
ou a biblioteca mudassem amanhã?
Erros comuns
| Erro | Sintoma | Correção |
|---|---|---|
Tratar o 404 do fetch no catch | o catch nunca roda; o corpo de erro é lido como se fosse o livro | examinar resposta.status depois do await |
Tratar todo AxiosError como falha de rede | "API fora do ar" para um livro que não existe | distinguir por erro.response; ou validateStatus |
JSON.stringify sem Content-Type no fetch | 400 com uma mensagem por campo, embora o corpo esteja completo | declarar application/json |
JSON.stringify no data do axios | texto enviado como application/x-www-form-urlencoded; 400 com property {"titulo":…} should not exist | passar o objeto |
await resposta.json() depois de um DELETE | SyntaxError: Unexpected end of JSON input | tratar o 204 antes de ler o corpo |
Ler o corpo do fetch duas vezes | TypeError: Body is unusable | guardar o resultado da primeira leitura |
Tempo limite com Promise.race | o programa desiste, mas o pedido segue aberto no servidor | AbortSignal.timeout ou timeout do axios |
axios.get<Livro>() tomado como validação | TypeError longe da chamada, quando o formato muda | unknown + guarda de tipo na fronteira |
| axios num componente de servidor chamado por página e metadata | duas requisições iguais por visita | cache do React, ou fetch |
fetchOptions: { cache: "no-store" } com o adaptador fetch do axios | build falha com Dynamic server usage embrulhado em AxiosError | connection() na rota, ou fetch |
"axios": "^1.x" sem lockfile | versão nova entra sem decisão | versão exata, lockfile versionado e npm ci |
Laboratórios
Os checkpoints da próxima seção verificam compreensão. Estes laboratórios verificam prática: quatro exercícios contra a API da aula 10 e um experimento opcional com o cliente web da aula 13.
Preparando o ambiente
Estado inicial esperado: a API da aula 10
(dev-web-mobile-nestjs/aula-10-autenticacao) rodando em
http://localhost:3000, com o banco populado por npx prisma db seed. Nenhum
arquivo da API é alterado nesta página.
Crie a pasta extra-b-fetch-axios, com as subpastas src, praticar e
next, e baixe os arquivos preservando nomes e pastas:
| Arquivo para download | Pasta de destino |
|---|---|
| package.json | raiz |
| package-lock.json | raiz |
| tsconfig.json | raiz |
| tsconfig.praticar.json | raiz |
| README.md | raiz |
| contar-requisicoes.cjs | raiz |
| src/api.ts | src |
| src/01-ler-com-fetch-e-axios.ts | src |
| src/02-tres-desfechos.ts | src |
| src/03-escrever-com-token.ts | src |
| src/04-tempo-limite-cancelamento.ts | src |
| src/05-cliente-configurado.ts | src |
| src/06-tipagem-da-resposta.ts | src |
| src/servidor-lento.ts | src |
| src/erro-da-api.ts | src |
| src/cliente-axios.ts | src |
| src/cliente-fetch.ts | src |
| praticar/01-tres-desfechos.ts | praticar |
| praticar/02-escrever.ts | praticar |
| praticar/03-tempo-limite.ts | praticar |
| praticar/04-cliente.ts | praticar |
| praticar/LEIA-ME.md | praticar |
| next/livros-axios.ts | next |
| next/livros-axios-cache.ts | next |
| next/lista-variantes.ts | next |
| next/pagina-livro-com-metadata.tsx | next |
Na pasta extra-b-fetch-axios, execute:
npm ci
npm run check
npx tsx src/01-ler-com-fetch-e-axios.ts
| Comando | O que faz |
|---|---|
npm ci | instala exatamente as versões do package-lock.json (Parte 8) |
npm run check | verifica os tipos dos exemplos de src |
npm run check:praticar | verifica também os exercícios de praticar |
npm run servidor-lento | sobe o servidor lento na porta 3100 (exemplo 04 e laboratório 3) |
npx tsx src/NN-arquivo.ts | executa um exemplo |
npx tsx praticar/NN-arquivo.ts | executa o seu exercício |
Como na Extra A, tsx executa TypeScript mas não verifica tipos, e os
arquivos usam ESM com module e moduleResolution em nodenext. Os
laboratórios 2 e 4 cadastram e removem o livro Quincas Borba; se um deles
parar no meio, o livro fica no banco e a execução seguinte recebe 409 — rode
npx prisma db seed na API para voltar ao estado inicial.
Node.js 22.22.2, TypeScript 6.0.3, tsx 4.20.5 e axios 1.20.0, contra a API de
aula-10-autenticacao com PostgreSQL 16. O experimento do laboratório 5 foi
feito com Next.js 16.3.4 e React 19.2.8, as versões do projeto
aula-13-dados-api.
Laboratório 1 — Os três desfechos
Arquivo: praticar/01-tres-desfechos.ts · Tempo: ~20 min
Implemente buscarComFetch e buscarComAxios com o contrato de buscarLivro
da aula 13: o livro, undefined para 404, erro para os demais status, e a
rejeição do cliente quando não há resposta. Antes de executar, escreva em
comentário o que espera em cada uma das oito linhas de saída.
No axios, implemente uma das duas formas pedidas no TODO 3 e explique em
comentário o que a outra faria diferente. Para o caso "API fora do ar", o
programa usa a porta 3999, onde nada escuta.
Laboratório 2 — Escrever com token
Arquivo: praticar/02-escrever.ts · Tempo: ~25 min
Implemente o login e o cadastro com fetch e preveja o status de cada uma das
seis tentativas antes de executar. Uma delas depende da ordem em que a API
aplica guard e validação: explique qual, e por quê.
Termine removendo o livro com axios e depois tentando o mesmo com fetch e
.json(). Registre em comentário a mensagem de erro e a correção.
Laboratório 3 — Tempo limite e cancelamento
Arquivo: praticar/03-tempo-limite.ts · Tempo: ~25 min · Precisa de: npm run servidor-lento em outro terminal
Implemente a busca da ficha com tempo limite nos dois clientes e confira no
terminal do servidor que as chamadas que estouraram aparecem como
ABANDONADO.
Depois implemente digitar: três buscas disparadas com 150 ms de intervalo,
em que cada letra nova cancela a busca anterior com um AbortController
guardado fora da função. O resultado esperado no terminal do servidor é dois
pedidos abandonados e um respondido. Em comentário, compare com a variável
ignorar da Parte 9 da aula 13: o que cada solução evita?
Laboratório 4 — Um cliente, duas implementações
Arquivo: praticar/04-cliente.ts · Tempo: ~30 min
src/cliente-axios.ts está pronto. Escreva criarMeuClienteFetch sem
consultar src/cliente-fetch.ts, seguindo os TODO 1 a 4, e o guarda
ehLivro do TODO 5. O roteiro no fim do arquivo roda contra os dois
clientes e imprime uma linha para cada um.
As duas linhas precisam ser iguais. Quando não forem, a diferença está no seu cliente: compare o desfecho que diverge com as tabelas das Partes 3 e 4.
Laboratório 5 (opcional) — O experimento no Next.js
Tempo: ~40 min · Precisa de: o projeto aula-13-dados-api do
repositório do cliente web
Refaça as medições da Parte 7:
-
Copie
aula-13-dados-apipara uma pasta nova — nunca altere o projeto da aula — e, na cópia, rodenpm installenpm i axios@1.20.0. -
Copie
next/livros-axios.ts,next/livros-axios-cache.tsenext/lista-variantes.tsparalib/da cópia, enext/pagina-livro-com-metadata.tsxpor cima deapp/livros/[id]/page.tsx. -
Pare a API e suba-a de novo com o contador de requisições, informando o caminho completo do arquivo:
NODE_OPTIONS="--require /caminho/para/extra-b-fetch-axios/contar-requisicoes.cjs" npm run start:dev -
Na cópia do cliente,
rm -rf .next && npm run build && npm run start, abrahttp://localhost:3001/livros/<id>e conte as linhas[requisição N]no terminal da API. Troque o import debuscarLivroentre@/lib/livros,@/lib/livros-axiose@/lib/livros-axios-cache, repetindo o build. -
Em
app/livros/page.tsx, retire oawait connection(), troquelistarLivrospor cada função delib/lista-variantes.tse compare o símbolo de/livrosna saída do build. A última variante deve falhar: leia a mensagem inteira e localize nela oAxiosError.
Critérios de conclusão
Os laboratórios estão completos quando:
- suas previsões do laboratório 1 acertam as oito linhas, ou você sabe explicar cada erro de previsão;
- você explica, no laboratório 2, por que a tentativa sem token e sem
Content-Typerecebe 401 e não 400; - o terminal do servidor lento mostra os pedidos abandonados do laboratório 3, e dois abandonados na busca "enquanto digita";
- as duas linhas do laboratório 4 são iguais e
npm run check:praticarpassa semanye sem@ts-ignore; - (opcional) você reproduziu as contagens 1, 2 e 1 da Parte 7 e a falha do build com o adaptador fetch.
Fechamento
Nenhuma das diferenças desta página é de sintaxe.
A primeira é o que conta como falha. fetch separa "houve resposta" de
"não houve"; axios separa "resposta boa" do resto. As duas regras funcionam,
desde que o código examine o status em algum lugar — e em um lugar só.
A segunda é quem faz o trabalho repetitivo: serializar, declarar o
formato, pôr o token, tratar o 204. O axios faz por configuração; com fetch,
uma função faz. Nos dois casos, o trabalho fica num módulo, e o resto do
projeto não sabe qual biblioteca está por baixo.
A terceira é o ambiente. No Node e no navegador, os dois clientes são
equivalentes. No servidor do Next.js, fetch é também a porta de entrada da
memoização e do cache, e trocá-lo exige declarar na rota o que antes se
declarava na chamada.
E a última é que uma dependência é uma decisão: sobre o que ela faz que o
projeto não faria, e sobre em quem se confia para publicar a próxima versão.
Versão fixa, lockfile e npm ci não dependem da biblioteca escolhida.
Exercícios (checkpoints)
-
Preveja, para cada chamada, se a promise realiza ou rejeita no
fetche no axios, e indique onde o status fica disponível: (a)GET /livros/999999; (b)GET /livros/abc; (c)GET /livroscom a API parada; (d)DELETE /livros/:idbem-sucedido. -
Explique por que o
fetchentrega a resposta em duas etapas e descreva uma situação em que isso é vantagem, não só sintaxe a mais. -
Diagnostique: um
POST /livroscomfetchrecebe 400 e nove mensagens de validação, mas o corpo, impresso no console antes do envio, tem todos os campos. Explique a causa, indique a correção e diga qual seria o status se o token também estivesse ausente, justificando pela ordem de guards e pipes. -
Reescreva com
fetcha chamadaaxios.delete(url, { headers })de modo que ela não lance erro ao receber 204 e lanceErroDaApiao receber 403 com o envelope de erro da aula 6. -
Compare
Promise.racecom um temporizador eAbortSignal.timeoutquanto ao que o servidor observa, e indique por que a diferença importa numa busca disparada a cada tecla. -
Justifique por que
axios.get<Livro>(url)não é mais seguro do que(await resposta.json()) as Livro, e escreva a assinatura da função que torna a verificação real. -
Identifique o que o interceptor de requisição e o de resposta do
cliente-axios.tsfazem, e aponte as linhas decliente-fetch.tsque fazem o mesmo papel. -
Explique por que uma página do Next.js que chama
buscarLivronogenerateMetadatae no componente faz uma requisição comfetche duas com axios, e indique duas formas de voltar a uma. -
Decida, com as perguntas da Parte 9, qual cliente usar e onde declarar o comportamento de cache em cada caso: (a) a listagem de um componente de servidor que deve refletir cada novo livro cadastrado; (b) um script de importação em Node que envia 500 livros à API; (c) um projeto que já tem um cliente axios configurado e precisa de uma chamada nova no navegador.
-
Avalie o
package.jsonabaixo quanto ao risco discutido na Parte 8 e reescreva a linha de dependência e o comando de instalação usado no servidor de integração:{ "dependencies": { "axios": "^1.14.0" } }
Referências
Principais
- MDN — Using the Fetch API — requisição, resposta, corpo e cabeçalhos
- MDN — Response.ok — por que um 404 não rejeita o
fetch - MDN — AbortSignal.timeout() — tempo limite com cancelamento real
- axios — documentação — instância, configuração da requisição, interceptors, tratamento de erros e cancelamento
- Next.js — fetch — as opções que o Next acrescenta e a memoização
- Next.js — Caching and Revalidating (sem Cache Components) — como tratar dados que não vêm do
fetch - React — cache — memoização de uma função durante a renderização no servidor
- axios — Post Mortem: axios npm supply chain compromise (issue 10636) — o relato dos mantenedores sobre as versões de 31/03/2026
Aprofundamento
- WHATWG — Fetch Standard — a especificação, incluindo o
Content-Typepadrão de cada tipo de corpo - MDN — HTTP response status codes — 204, 400, 401, 403, 404, 409
- npm — npm ci — instalação a partir do lockfile
- StepSecurity — axios compromised on npm — análise técnica independente do incidente, com o mecanismo de
postinstall