Para consumir a API do CPFHub.io em TypeScript com tipagem segura, defina interfaces que espelhem o contrato da resposta, use Zod para validação em runtime e encapsule as chamadas em um client reutilizável com tratamento de erros tipado. Essa abordagem elimina bugs de acesso a campos inexistentes, oferece autocompletar completo no editor e torna o código mais fácil de manter.
Introdução
TypeScript se consolidou como a linguagem padrão para desenvolvimento web profissional, oferecendo tipagem estática que detecta erros em tempo de compilação e melhora a experiência do desenvolvedor com autocompletar e documentação inline. Quando se trata de consumir APIs externas, a tipagem segura garante que a aplicação trata corretamente todos os campos da resposta, evitando bugs sutis que só apareceriam em produção.
Pré-requisitos
-
Node.js 18+ -- Para suporte nativo a fetch.
-
TypeScript 5+ -- Para recursos modernos de tipagem.
-
Conta no CPFHub.io -- Crie uma conta gratuita em cpfhub.io
Instalação
npm init -y
npm install typescript zod
npm install -D @types/node tsx
npx tsc --init
Definindo as interfaces
O primeiro passo é definir interfaces TypeScript que representam a estrutura da resposta da API.
Tipos da API (types.ts)
// types.ts
export interface CPFData {
cpf: string;
name: string;
nameUpper: string;
gender: 'M' | 'F';
birthDate: string;
day: number;
month: number;
year: number;
}
export interface CPFHubSuccessResponse {
success: true;
data: CPFData;
}
export interface CPFHubErrorResponse {
success: false;
error?: string;
}
export type CPFHubResponse = CPFHubSuccessResponse | CPFHubErrorResponse;
export interface CPFHubClientConfig {
apiKey: string;
baseUrl?: string;
timeout?: number;
}
export type CPFHubErrorCode =
| 'INVALID_FORMAT'
| 'NOT_FOUND'
| 'RATE_LIMITED'
| 'UNAUTHORIZED'
| 'TIMEOUT'
| 'NETWORK_ERROR'
| 'UNKNOWN';
export class CPFHubError extends Error {
constructor(
message: string,
public readonly code: CPFHubErrorCode,
public readonly statusCode?: number
) {
super(message);
this.name = 'CPFHubError';
}
}
Validação com Zod
Interfaces TypeScript existem apenas em tempo de compilação. Para garantir que a resposta da API realmente corresponde à interface esperada, usamos Zod para validação em runtime.
Schemas Zod (schemas.ts)
// schemas.ts
import { z } from 'zod';
export const CPFDataSchema = z.object({
cpf: z.string().length(11),
name: z.string().min(1),
nameUpper: z.string().min(1),
gender: z.enum(['M', 'F']),
birthDate: z.string().regex(/^\d{2}\/\d{2}\/\d{4}$/),
day: z.number().int().min(1).max(31),
month: z.number().int().min(1).max(12),
year: z.number().int().min(1900).max(2100),
});
export const CPFHubSuccessResponseSchema = z.object({
success: z.literal(true),
data: CPFDataSchema,
});
export const CPFHubErrorResponseSchema = z.object({
success: z.literal(false),
error: z.string().optional(),
});
export const CPFHubResponseSchema = z.discriminatedUnion('success', [
CPFHubSuccessResponseSchema,
CPFHubErrorResponseSchema,
]);
// Validação de CPF (formato e dígitos verificadores)
export const CPFInputSchema = z
.string()
.transform((val) => val.replace(/\D/g, ''))
.refine((val) => val.length === 11, {
message: 'CPF deve ter 11 dígitos',
})
.refine((val) => !/^(\d)\1{10}$/.test(val), {
message: 'CPF com todos os dígitos iguais é inválido',
})
.refine(
(val) => {
// Primeiro dígito verificador
let soma = 0;
for (let i = 0; i < 9; i++) {
soma += parseInt(val[i]) * (10 - i);
}
let resto = (soma * 10) % 11;
if (resto === 10) resto = 0;
if (resto !== parseInt(val[9])) return false;
// Segundo dígito verificador
soma = 0;
for (let i = 0; i < 10; i++) {
soma += parseInt(val[i]) * (11 - i);
}
resto = (soma * 10) % 11;
if (resto === 10) resto = 0;
return resto === parseInt(val[10]);
},
{ message: 'Dígitos verificadores inválidos' }
);
Client reutilizável (client.ts)
O client encapsula toda a lógica de comunicação com a API, com tipagem completa e tratamento de erros.
// client.ts
import {
CPFHubClientConfig,
CPFHubResponse,
CPFHubSuccessResponse,
CPFData,
CPFHubError,
} from './types';
import { CPFHubResponseSchema, CPFInputSchema } from './schemas';
const DEFAULT_BASE_URL = 'https://api.cpfhub.io/cpf';
const DEFAULT_TIMEOUT = 10000;
export class CPFHubClient {
private readonly apiKey: string;
private readonly baseUrl: string;
private readonly timeout: number;
constructor(config: CPFHubClientConfig) {
this.apiKey = config.apiKey;
this.baseUrl = config.baseUrl ?? DEFAULT_BASE_URL;
this.timeout = config.timeout ?? DEFAULT_TIMEOUT;
}
async consultarCPF(cpfInput: string): Promise<CPFData> {
// Validar e limpar CPF
const parseResult = CPFInputSchema.safeParse(cpfInput);
if (!parseResult.success) {
throw new CPFHubError(
parseResult.error.errors[0].message,
'INVALID_FORMAT'
);
}
const cpf = parseResult.data;
// Fazer requisição
let response: Response;
try {
response = await fetch(`${this.baseUrl}/${cpf}`, {
method: 'GET',
headers: {
'x-api-key': this.apiKey,
Accept: 'application/json',
},
signal: AbortSignal.timeout(this.timeout),
});
} catch (error) {
if (error instanceof DOMException && error.name === 'TimeoutError') {
throw new CPFHubError(
'Timeout na consulta ao CPFHub.io',
'TIMEOUT'
);
}
throw new CPFHubError(
`Erro de conexão: ${(error as Error).message}`,
'NETWORK_ERROR'
);
}
// Tratar códigos de erro HTTP
if (response.status === 401) {
throw new CPFHubError('Chave de API inválida ou ausente', 'UNAUTHORIZED', 401);
}
if (response.status === 429) {
throw new CPFHubError('Rate limit excedido', 'RATE_LIMITED', 429);
}
// Parsear resposta
const json: unknown = await response.json();
// Validar com Zod
const validationResult = CPFHubResponseSchema.safeParse(json);
if (!validationResult.success) {
throw new CPFHubError(
'Resposta da API com formato inesperado',
'UNKNOWN',
response.status
);
}
const resultado: CPFHubResponse = validationResult.data;
if (!resultado.success) {
throw new CPFHubError('CPF não encontrado', 'NOT_FOUND', 404);
}
return resultado.data;
}
async verificarCPF(
cpfInput: string,
nomeEsperado?: string
): Promise<{
valido: boolean;
dados: CPFData;
nomeConfere: boolean;
}> {
const dados = await this.consultarCPF(cpfInput);
let nomeConfere = true;
if (nomeEsperado) {
const nomeUpper = nomeEsperado.toUpperCase().trim();
nomeConfere =
dados.nameUpper.includes(nomeUpper) ||
nomeUpper.includes(dados.nameUpper);
}
return {
valido: true,
dados,
nomeConfere,
};
}
}
Usando o client
Exemplo básico (main.ts)
// main.ts
import { CPFHubClient, CPFHubError } from './client';
const client = new CPFHubClient({
apiKey: process.env.CPFHUB_API_KEY!,
});
async function main() {
try {
// Consulta simples
const dados = await client.consultarCPF('123.456.789-00');
console.log(`Nome: ${dados.name}`);
console.log(`Nascimento: ${dados.birthDate}`);
console.log(`Gênero: ${dados.gender}`);
// Verificação com match de nome
const verificacao = await client.verificarCPF(
'12345678900',
'João da Silva'
);
console.log(`CPF válido: ${verificacao.valido}`);
console.log(`Nome confere: ${verificacao.nomeConfere}`);
} catch (error) {
if (error instanceof CPFHubError) {
console.error(`Erro [${error.code}]: ${error.message}`);
switch (error.code) {
case 'INVALID_FORMAT':
console.error('Verifique o formato do CPF informado.');
break;
case 'NOT_FOUND':
console.error('CPF não encontrado na base.');
break;
case 'RATE_LIMITED':
console.error('Aguarde antes de fazer nova consulta.');
break;
case 'UNAUTHORIZED':
console.error('Verifique sua chave de API.');
break;
case 'TIMEOUT':
console.error('A API demorou para responder.');
break;
default:
console.error('Erro inesperado.');
}
}
}
}
main();
Executando
CPFHUB_API_KEY=SUA_CHAVE_DE_API npx tsx main.ts
Usando em projetos Express com TypeScript
import express from 'express';
import { CPFHubClient, CPFHubError } from './client';
const app = express();
app.use(express.json());
const cpfClient = new CPFHubClient({
apiKey: process.env.CPFHUB_API_KEY!,
});
app.get('/cpf/:cpf', async (req, res) => {
try {
const dados = await cpfClient.consultarCPF(req.params.cpf);
res.json({ success: true, data: dados });
} catch (error) {
if (error instanceof CPFHubError) {
const statusMap: Record<string, number> = {
INVALID_FORMAT: 400,
NOT_FOUND: 404,
RATE_LIMITED: 429,
UNAUTHORIZED: 500,
TIMEOUT: 504,
NETWORK_ERROR: 502,
UNKNOWN: 500,
};
res.status(statusMap[error.code] ?? 500).json({
success: false,
error: error.message,
code: error.code,
});
}
}
});
app.listen(3000);
Benefícios da tipagem segura
| Aspecto | Sem tipagem | Com tipagem TypeScript |
|---|---|---|
| Erros em runtime | Frequentes | Detectados na compilação |
| Autocompletar no editor | Limitado | Completo |
| Refatoração | Arriscada | Segura |
| Documentação | Manual | Inline via tipos |
| Validação de resposta | Inexistente | Zod em runtime |
A combinação de TypeScript (compilação) e Zod (runtime) garante que tanto o código quanto os dados estejam corretos em todas as etapas.
Boas práticas
-
Nunca use
any-- Tipar todos os retornos e parâmetros. -
Valide em runtime -- Use Zod ou similar para validar respostas de APIs externas.
-
Centralize o client -- Um único módulo de client para toda a aplicação.
-
Trate todos os erros -- O discriminated union de erros permite tratamento exaustivo.
-
Configure o timeout -- O CPFHub.io responde em ~900ms; um timeout de 10 segundos é adequado.
Perguntas frequentes
Por que usar Zod além das interfaces TypeScript para validar a resposta da API?
Interfaces TypeScript são apagadas em tempo de execução — elas existem apenas durante a compilação. Se a API retornar um campo com tipo diferente do esperado (por exemplo, birthDate como null em vez de string), o TypeScript não detectará o problema em produção. O Zod valida a estrutura dos dados em runtime, garantindo que qualquer desvio do contrato da API seja capturado e tratado antes de propagar um bug.
Como lidar com o código de erro RATE_LIMITED no CPFHub.io?
O CPFHub.io não bloqueia chamadas ao atingir o limite do plano gratuito — cobra R$0,15 por consulta adicional. O status 429 pode ocorrer em caso de excesso de requisições simultâneas em janelas de tempo muito curtas. Ao receber esse código, implemente backoff exponencial antes de tentar novamente, ou revise se há chamadas duplicadas desnecessárias no fluxo da aplicação.
É possível usar este client em projetos NestJS?
Sim. A classe CPFHubClient pode ser encapsulada em um @Injectable() do NestJS, com a chave de API injetada via ConfigService. Registre-a como provider no módulo correspondente e injete-a nos services que precisam validar CPF, mantendo a mesma lógica de tratamento de erros tipados.
Como garantir conformidade com a LGPD ao processar CPF em TypeScript?
Não persista o CPF em logs de aplicação — use a ANPD como referência para determinar bases legais de tratamento. No código, evite serializar o objeto CPFData completo em logs de debug; prefira registrar apenas o resultado da validação (sucesso/falha) e um identificador interno que não seja o CPF em si.
Conclusão
Consumir a API do CPFHub.io em TypeScript com tipagem completa e validação Zod resulta em um código mais robusto, fácil de manter e menos sujeito a surpresas em produção. A combinação de interfaces em tempo de compilação com schemas Zod em runtime cobre todos os pontos de falha possíveis ao integrar uma API externa, desde formatos inesperados de resposta até erros de rede e timeout. O client reutilizável apresentado aqui pode ser adaptado para qualquer framework Node.js ou ambiente de backend TypeScript.
Cadastre-se em cpfhub.io — 50 consultas mensais gratuitas, sem cartão de crédito — e integre validação de CPF com tipagem segura no seu projeto TypeScript.
CPFHub.io
Pronto para integrar a API?
50 consultas gratuitas para testar agora. Sem cartão de crédito. Acesso imediato à documentação.
Sobre a redação
Redação CPFHub.io
Time editorial especializado em APIs de CPF, identidade digital e compliance no mercado brasileiro. Produzimos guias técnicos, análises regulatórias e tutoriais sobre LGPD e KYC para desenvolvedores e líderes de produto.



