Pular para o conteúdo principal

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).

Material extra e opcional

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-Type esquecido, 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 as sobre resposta.json() não conferem a resposta, e aplicar um guarda de tipo na fronteira.
  • Comparar o comportamento de fetch e 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:

PerguntaOnde
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
Como executar os exemplos

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 HTTPfetchaxios
métodomethod: "POST"axios.post(...) ou method: "post"
URL e query stringtexto montado; URLSearchParamsurl + params (objeto)
cabeçalhos da requisiçãoheadersheaders
corpo da requisiçãobody, já serializadodata, objeto serializado pela biblioteca
status da respostaresposta.status, resposta.okresposta.status
cabeçalhos da respostaresposta.headers.get("content-type")resposta.headers["content-type"]
corpo da respostaawait resposta.json(), lido à parteresposta.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ó. bodyUsed passa a true depois do .json(); um segundo .json() na mesma resposta rejeita com TypeError: 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 com resposta.clone().
  • A query string é responsabilidade de alguém. Concatenar `?autor=${autor}` à mão funciona até o primeiro autor com & ou # no nome. URLSearchParams, no fetch, e params, 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:

Desfechofetchaxios
resposta 2xxrealiza; ok verdadeirorealiza
resposta 4xx ou 5xxrealiza; ok falsorejeita com AxiosError; erro.response preenchido; code ERR_BAD_REQUEST (4xx) ou ERR_BAD_RESPONSE (5xx)
nenhuma respostarejeita com TypeError; o código do sistema em erro.causerejeita 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 do await, 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 com erro.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.

É do padrão; é do domínio

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.

Tarefafetchaxios
serializar o corpoJSON.stringify(corpo)automático, para objeto
declarar o formato"Content-Type": "application/json"automático, para objeto
enviar o tokenAuthorization: Bearer … em headersidem, 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çãofetchaxiosRejeita com
tempo limitesignal: AbortSignal.timeout(ms)timeout: msTimeoutError · AxiosError com code ECONNABORTED
cancelamento pelo programasignal: controle.signal + controle.abort()a mesma opção signalAbortError · CanceledError, reconhecido por isCancel e code ERR_CANCELED
as duas coisas juntasAbortSignal.any([controle.signal, AbortSignal.timeout(ms)])signal + timeouto 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".

É do padrão; é do domínio

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 GET de fetch com 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 e generateMetadata.
  • Controle de cache por chamada. As opções cache e next.revalidate da aula 13 (Parte 4) existem porque o Next as lê no fetch. 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 deRequisições à API por visita a /livros/3
lib/livros.ts (fetch, aula 13)1
lib/livros-axios.ts2
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 usaSí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 adaptador fetch do axios não resolve

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áticaO 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 versionadoa 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áquinaa 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:

  1. 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 o fetch já faz.
  2. 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.
  3. 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ériofetchaxios
instalaçãonenhuma27 pacotes, versão a fixar
status fora da faixa 2xxrealiza; examinar okrejeita; validateStatus muda a regra
corpo JSONJSON.stringify + Content-Typeautomático para objeto
resposta 204não chamar .json()data vazio
tempo limiteAbortSignal.timeouttimeout
cancelamentoAbortControllerAbortController
configuração comumuma funçãoinstância e interceptors
tipo da respostaany; conferir com guardaparâmetro de tipo, sem conferência; conferir com guarda
componente de servidor do Nextmemoização e cache por chamadanem 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.

No seu projeto

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​

ErroSintomaCorreção
Tratar o 404 do fetch no catcho catch nunca roda; o corpo de erro é lido como se fosse o livroexaminar resposta.status depois do await
Tratar todo AxiosError como falha de rede"API fora do ar" para um livro que não existedistinguir por erro.response; ou validateStatus
JSON.stringify sem Content-Type no fetch400 com uma mensagem por campo, embora o corpo esteja completodeclarar application/json
JSON.stringify no data do axiostexto enviado como application/x-www-form-urlencoded; 400 com property {"titulo":…} should not existpassar o objeto
await resposta.json() depois de um DELETESyntaxError: Unexpected end of JSON inputtratar o 204 antes de ler o corpo
Ler o corpo do fetch duas vezesTypeError: Body is unusableguardar o resultado da primeira leitura
Tempo limite com Promise.raceo programa desiste, mas o pedido segue aberto no servidorAbortSignal.timeout ou timeout do axios
axios.get<Livro>() tomado como validaçãoTypeError longe da chamada, quando o formato mudaunknown + guarda de tipo na fronteira
axios num componente de servidor chamado por página e metadataduas requisições iguais por visitacache do React, ou fetch
fetchOptions: { cache: "no-store" } com o adaptador fetch do axiosbuild falha com Dynamic server usage embrulhado em AxiosErrorconnection() na rota, ou fetch
"axios": "^1.x" sem lockfileversão nova entra sem decisãoversã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 downloadPasta de destino
package.jsonraiz
package-lock.jsonraiz
tsconfig.jsonraiz
tsconfig.praticar.jsonraiz
README.mdraiz
contar-requisicoes.cjsraiz
src/api.tssrc
src/01-ler-com-fetch-e-axios.tssrc
src/02-tres-desfechos.tssrc
src/03-escrever-com-token.tssrc
src/04-tempo-limite-cancelamento.tssrc
src/05-cliente-configurado.tssrc
src/06-tipagem-da-resposta.tssrc
src/servidor-lento.tssrc
src/erro-da-api.tssrc
src/cliente-axios.tssrc
src/cliente-fetch.tssrc
praticar/01-tres-desfechos.tspraticar
praticar/02-escrever.tspraticar
praticar/03-tempo-limite.tspraticar
praticar/04-cliente.tspraticar
praticar/LEIA-ME.mdpraticar
next/livros-axios.tsnext
next/livros-axios-cache.tsnext
next/lista-variantes.tsnext
next/pagina-livro-com-metadata.tsxnext

Na pasta extra-b-fetch-axios, execute:

npm ci
npm run check
npx tsx src/01-ler-com-fetch-e-axios.ts
ComandoO que faz
npm ciinstala exatamente as versões do package-lock.json (Parte 8)
npm run checkverifica os tipos dos exemplos de src
npm run check:praticarverifica também os exercícios de praticar
npm run servidor-lentosobe o servidor lento na porta 3100 (exemplo 04 e laboratório 3)
npx tsx src/NN-arquivo.tsexecuta um exemplo
npx tsx praticar/NN-arquivo.tsexecuta 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.

Ambiente verificado

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:

  1. Copie aula-13-dados-api para uma pasta nova — nunca altere o projeto da aula — e, na cópia, rode npm install e npm i axios@1.20.0.

  2. Copie next/livros-axios.ts, next/livros-axios-cache.ts e next/lista-variantes.ts para lib/ da cópia, e next/pagina-livro-com-metadata.tsx por cima de app/livros/[id]/page.tsx.

  3. 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
  4. Na cópia do cliente, rm -rf .next && npm run build && npm run start, abra http://localhost:3001/livros/<id> e conte as linhas [requisição N] no terminal da API. Troque o import de buscarLivro entre @/lib/livros, @/lib/livros-axios e @/lib/livros-axios-cache, repetindo o build.

  5. Em app/livros/page.tsx, retire o await connection(), troque listarLivros por cada função de lib/lista-variantes.ts e compare o símbolo de /livros na saída do build. A última variante deve falhar: leia a mensagem inteira e localize nela o AxiosError.

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-Type recebe 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:praticar passa sem any e 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)​

  1. Preveja, para cada chamada, se a promise realiza ou rejeita no fetch e no axios, e indique onde o status fica disponível: (a) GET /livros/999999; (b) GET /livros/abc; (c) GET /livros com a API parada; (d) DELETE /livros/:id bem-sucedido.

  2. Explique por que o fetch entrega a resposta em duas etapas e descreva uma situação em que isso é vantagem, não só sintaxe a mais.

  3. Diagnostique: um POST /livros com fetch recebe 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.

  4. Reescreva com fetch a chamada axios.delete(url, { headers }) de modo que ela não lance erro ao receber 204 e lance ErroDaApi ao receber 403 com o envelope de erro da aula 6.

  5. Compare Promise.race com um temporizador e AbortSignal.timeout quanto ao que o servidor observa, e indique por que a diferença importa numa busca disparada a cada tecla.

  6. 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.

  7. Identifique o que o interceptor de requisição e o de resposta do cliente-axios.ts fazem, e aponte as linhas de cliente-fetch.ts que fazem o mesmo papel.

  8. Explique por que uma página do Next.js que chama buscarLivro no generateMetadata e no componente faz uma requisição com fetch e duas com axios, e indique duas formas de voltar a uma.

  9. 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.

  10. Avalie o package.json abaixo 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​

Aprofundamento​