Pular para o conteúdo principal

Semana 1 — A camada de serviços (Axios): um lugar só para falar com a API

· 4 min para ler
Diário de Aula da turma

Na primeira semana montamos a base de tudo que vem depois: uma camada de serviços em Axios que centraliza como o app conversa com a API da turma — instância única, headers automáticos, injeção de token e tratamento de erro num lugar só.

A ideia da semana: nenhuma tela chama axios direto. A tela pede a um serviço; o serviço sabe falar com a API. Assim, quando algo muda (URL, header, erro), muda num arquivo só.

Por que não chamar a API direto na tela​

Se cada tela montar sua própria requisição, a gente repete headers, repete try/catch, repete o jeito de ler o erro — e no dia que a API muda, caça-se fetch por todo o app. A camada de serviços resolve isso: um cliente HTTP configurado, e funções tipadas por cima dele.

O que construímos​

1. A instância HTTP — src/services/http.ts​

Um cliente Axios com baseURL, timeout e os headers que vão em toda requisição: X-API-Key (identifica o grupo) e X-Student-RM (rastreia quem está codando — isso conta na avaliação).

export const http = axios.create({
baseURL: `${env.apiUrl}/v1`,
timeout: 15000,
headers: {
'Content-Type': 'application/json',
'X-API-Key': env.apiKey,
'X-Student-RM': env.studentRm,
},
});

2. Interceptor de request — o token entra sozinho​

O comprador loga e recebe um token. Em vez de lembrar de anexá-lo em cada chamada, um interceptor injeta o Authorization: Bearer quando existe token, e o remove quando não:

let customerToken: string | null = null;
export function setCustomerToken(token: string | null) { customerToken = token; }

http.interceptors.request.use((config) => {
if (customerToken) config.headers.set('Authorization', `Bearer ${customerToken}`);
else config.headers.delete('Authorization');
return config;
});

3. Interceptor de response — um tratamento de erro para o app inteiro​

Aqui está o pulo do gato da semana: toda falha vira um ApiError com code, message e status. A tela nunca precisa cavar error.response.data.error — ela recebe um erro previsível e tipado.

http.interceptors.response.use(
(response) => response,
(error) => {
const status = error.response?.status ?? 0;
const payload = error.response?.data?.error;
if (payload) return Promise.reject(new ApiError(payload.code ?? 'ERROR', payload.message ?? 'Erro na API', status));
if (error.code === 'ECONNABORTED') return Promise.reject(new ApiError('TIMEOUT', 'A requisição demorou demais.', status));
return Promise.reject(new ApiError('NETWORK_ERROR', 'Sem conexão com o servidor. Confira a URL da API.', status));
},
);

O caminho de uma requisição, de ponta a ponta:

4. Serviços tipados por recurso​

Por cima da instância, funções pequenas e tipadas — a tela pede o dado, não sabe de HTTP:

// src/services/products.ts
export async function listProducts(params: ListProductsParams = {}): Promise<Paginated<ProductSummary>> {
const { data } = await http.get<Paginated<ProductSummary>>('/products', { params });
return data;
}
export async function getProduct(id: string): Promise<Product> {
const { data } = await http.get<Product>(`/products/${id}`);
return data;
}

Também deixamos prontos cart.ts (getCart, addCartItem, updateCartItem, removeCartItem) e auth.ts (login, register) — todos no mesmo padrão.

5. Os tipos da API — src/types/api.ts​

Paginated<T>, ProductSummary, Product (com variants[]), Cart, e a classe ApiError. Tipar aqui é o que faz o autocomplete funcionar no app inteiro e mata o any.

As telas (consumindo "na mão")​

Com os serviços prontos, as telas de lista e detalhe buscam com useState + useEffect, tratando loading/erro na unha. O carrinho ficou como placeholder.

Funciona — mas repare em quanta coisa a tela gerencia:

const [produtos, setProdutos] = useState<ProductSummary[]>([]);
const [loading, setLoading] = useState(true);
const [erro, setErro] = useState<string | null>(null);

useEffect(() => {
let vivo = true;
listProducts()
.then((res) => vivo && setProdutos(res.data))
.catch((e) => vivo && setErro(e.message))
.finally(() => vivo && setLoading(false));
return () => { vivo = false; };
}, []);

Gancho para a Semana 2: guardem essa sensação de "gerenciar dado na mão". É exatamente isso que o TanStack Query vem aposentar — cache, revalidação e estados prontos. A camada de serviços continua; o Query só orquestra o que ela já faz.

Regras de ouro (valem o semestre todo)​

  • A unidade vendável é a VARIANTE. Carrinho usa variantId; preço/estoque vêm em product.variants[] (no detalhe), não na listagem.
  • Headers sempre: X-API-Key e X-Student-RM. Rotas de comprador exigem também o Authorization: Bearer (que o interceptor injeta).
  • Sem any sem justificativa documentada — é penalidade nos checkpoints.

Lembretes de API​

  • Base (nuvem): https://api.mockmerce.com.br/v1. Os apps conectam no backend na nuvem, não em localhost — no app, isso vem de env.apiUrl.
  • Documentação viva (Swagger): https://api.mockmerce.com.br/docs.