Problema
Cliente mobile assume rede estável, não trata 401 de refresh expirado e duplica URLs e headers em dezenas de arquivos. Backend muda contrato sem versionamento e o app quebra silenciosamente.
Solução
Cliente HTTP único com interceptors: injeta auth, renova token uma vez por 401 em fila, propaga X-Request-Id. Backend: versionamento /v1, erros JSON consistentes, rate limit por device/user.
Arquitetura
RN app → ApiClient (fetch/axios) → API Node (Nest/Express) → DB
↘ refresh em Keychain/Keystore
- Timeouts e cancelamento com AbortController em navegações.
- Retry só para idempotentes (GET) ou com idempotency-key em POST críticos.
Código
export async function apiGet<T>(path: string, token: string): Promise<T> {
const ctrl = new AbortController();
const t = setTimeout(() => ctrl.abort(), 12_000);
try {
const res = await fetch(`${BASE}/v1${path}`, {
signal: ctrl.signal,
headers: { Authorization: `Bearer ${token}` },
});
if (res.status === 401) {
const fresh = await refreshSession();
return apiGet(path, fresh.accessToken);
}
if (!res.ok) throw await ApiError.fromResponse(res);
return (await res.json()) as T;
} finally {
clearTimeout(t);
}
}Performance
Keep-alive no native quando suportado; compressão; paginação server-side; evitar payloads grandes em listas.
Melhorias futuras
Certificate pinning para apps sensíveis; graphql apenas se o time domina operações; sync background (Expo TaskManager) onde fizer sentido.
Conclusão
Integração mobile + Node é contrato + resiliência. Mostrar isso reforça narrativa full stack sólida.