Como consumir API de CPF em Fastify com plugins e decorators

Aprenda a consumir a API de consulta de CPF da CPFHub.io em Fastify usando plugins, decorators e schemas para validação de alto desempenho.

Redação CPFHub.io
Redação CPFHub.io
··7 min de leitura
Como consumir API de CPF em Fastify com plugins e decorators

O Fastify é um dos frameworks Node.js mais rápidos disponíveis, projetado para alto desempenho com baixo overhead. Sua arquitetura baseada em plugins e decorators permite encapsular a integração com a API de CPF de forma modular e reutilizável. Com schemas JSON integrados, o Fastify valida parâmetros de entrada e serializa respostas automaticamente, sem configuração extra.


1. Pré-requisitos

  • Node.js 18+ instalado.

  • Fastify instalado (npm install fastify).

  • Pacotes adicionais: @fastify/env, @fastify/rate-limit.

  • Uma conta na CPFHub.io


2. Instale as dependências

npm init -y
npm install fastify @fastify/env @fastify/rate-limit

3. Configure as variáveis de ambiente

Crie o arquivo .env:

CPFHUB_API_KEY=SUA_CHAVE_DE_API
CPFHUB_BASE_URL=https://api.cpfhub.io
CPFHUB_TIMEOUT=5000
PORT=3000

4. Plugin de integração com a CPFHub.io

O plugin encapsula toda a lógica de integração e expõe um decorator na instância do Fastify:

// plugins/cpfhub.js
const fp = require('fastify-plugin');

async function cpfhubPlugin(fastify, options) {
    const { apiKey, baseUrl, timeout } = options;

    async function consultarCpf(cpf) {
    const cpfLimpo = cpf.replace(/\D/g, '');

    if (cpfLimpo.length !== 11) {
    throw fastify.httpErrors.badRequest('CPF deve conter 11 dígitos');
    }

    const controller = new AbortController();
    const timeoutId = setTimeout(() => controller.abort(), timeout);

    try {
    const response = await fetch(`${baseUrl}/cpf/${cpfLimpo}`, {
    method: 'GET',
    headers: {
    'x-api-key': apiKey,
    'Accept': 'application/json'
    },
    signal: controller.signal
    });

    clearTimeout(timeoutId);

    if (!response.ok) {
    const mensagens = {
    400: 'CPF com formato inválido',
    401: 'Chave de API inválida ou ausente',
    404: 'CPF não encontrado'
    };

    const msg = mensagens[response.status] || `Erro HTTP ${response.status}`;

    if (response.status === 401) throw fastify.httpErrors.unauthorized(msg);
    if (response.status === 404) throw fastify.httpErrors.notFound(msg);
    throw fastify.httpErrors.badRequest(msg);
    }

    return await response.json();
    } catch (error) {
    clearTimeout(timeoutId);

    if (error.statusCode) throw error; // Já é um httpError

    if (error.name === 'AbortError') {
    throw fastify.httpErrors.gatewayTimeout('Timeout na consulta de CPF');
    }

    throw fastify.httpErrors.badGateway('Falha na conexão com a API de CPF');
    }
    }

    // Decorator: disponibiliza cpfhub.consultarCpf em todas as rotas
    fastify.decorate('cpfhub', { consultarCpf });
}

module.exports = fp(cpfhubPlugin, {
    name: 'cpfhub',
    dependencies: ['@fastify/sensible']
});

O uso de fastify-plugin (fp) garante que o decorator seja acessível em todo o escopo da aplicação, não apenas no contexto do plugin.


5. Schemas de validação e serialização

Defina schemas JSON para validar parâmetros de entrada e serializar a resposta:

// schemas/cpf.js
const cpfParamsSchema = {
    type: 'object',
    required: ['cpf'],
    properties: {
    cpf: {
    type: 'string',
    pattern: '^\\d{11}$',
    description: 'CPF com 11 dígitos numéricos'
    }
    }
};

const cpfResponseSchema = {
    type: 'object',
    properties: {
    success: { type: 'boolean' },
    data: {
    type: 'object',
    properties: {
    cpf: { type: 'string' },
    name: { type: 'string' },
    nameUpper: { type: 'string' },
    gender: { type: 'string' },
    birthDate: { type: 'string' },
    day: { type: 'integer' },
    month: { type: 'integer' },
    year: { type: 'integer' }
    }
    }
    }
};

module.exports = { cpfParamsSchema, cpfResponseSchema };

6. Aplicação completa

// app.js
require('dotenv').config();
const fastify = require('fastify')({
    logger: true
});

const { cpfParamsSchema, cpfResponseSchema } = require('./schemas/cpf');

async function build() {
    // Plugins essenciais
    await fastify.register(require('@fastify/sensible'));

    // Rate limiting
    await fastify.register(require('@fastify/rate-limit'), {
    max: 30,
    timeWindow: '1 minute'
    });

    // Plugin CPFHub
    await fastify.register(require('./plugins/cpfhub'), {
    apiKey: process.env.CPFHUB_API_KEY,
    baseUrl: process.env.CPFHUB_BASE_URL,
    timeout: parseInt(process.env.CPFHUB_TIMEOUT || '5000', 10)
    });

    // Rota de consulta de CPF
    fastify.get('/api/cpf/:cpf', {
    schema: {
    params: cpfParamsSchema,
    response: {
    200: cpfResponseSchema
    }
    }
    }, async (request, reply) => {
    const { cpf } = request.params;
    const resultado = await fastify.cpfhub.consultarCpf(cpf);
    return resultado;
    });

    // Health check
    fastify.get('/health', async () => {
    return { status: 'ok' };
    });

    return fastify;
}

build().then((app) => {
    app.listen({ port: parseInt(process.env.PORT || '3000', 10), host: '0.0.0.0' }, (err) => {
    if (err) {
    app.log.error(err);
    process.exit(1);
    }
    });
});

7. Teste com cURL

node app.js
curl -X GET http://localhost:3000/api/cpf/12345678900 \
    -H "Accept: application/json"

Resposta esperada:

{
    "success": true,
    "data": {
    "cpf": "12345678900",
    "name": "João da Silva",
    "nameUpper": "JOÃO DA SILVA",
    "gender": "M",
    "birthDate": "15/06/1990",
    "day": 15,
    "month": 6,
    "year": 1990
    }
}

Se o CPF não tiver 11 dígitos, o schema de validação retorna automaticamente um erro 400 antes de executar o handler.


8. Plugin de cache

Crie um plugin de cache para evitar consultas repetidas:

// plugins/cpfCache.js
const fp = require('fastify-plugin');

async function cpfCachePlugin(fastify, options) {
    const cache = new Map();
    const ttl = options.ttl || 3600000; // 1 hora padrão

    fastify.decorate('cpfCache', {
    get(cpf) {
    const entry = cache.get(cpf);
    if (entry && Date.now() - entry.timestamp < ttl) {
    return entry.data;
    }
    cache.delete(cpf);
    return null;
    },

    set(cpf, data) {
    cache.set(cpf, { data, timestamp: Date.now() });
    }
    });
}

module.exports = fp(cpfCachePlugin, { name: 'cpfCache' });

Use na rota:

fastify.get('/api/cpf/:cpf', {
    schema: { params: cpfParamsSchema, response: { 200: cpfResponseSchema } }
}, async (request, reply) => {
    const { cpf } = request.params;

    // Verificar cache
    const cached = fastify.cpfCache.get(cpf);
    if (cached) return cached;

    // Consultar API
    const resultado = await fastify.cpfhub.consultarCpf(cpf);

    // Salvar no cache
    if (resultado.success) {
    fastify.cpfCache.set(cpf, resultado);
    }

    return resultado;
});

9. Hook para logging de consultas

Use hooks do Fastify para registrar cada consulta:

fastify.addHook('onResponse', (request, reply, done) => {
    if (request.url.startsWith('/api/cpf/')) {
    fastify.log.info({
    cpf: request.params.cpf,
    statusCode: reply.statusCode,
    responseTime: reply.elapsedTime
    }, 'Consulta de CPF registrada');
    }
    done();
});

10. Boas práticas

  • Plugins -- Encapsule a integração com a CPFHub.io em um plugin reutilizável. O padrão de plugin do Fastify facilita testes e compartilhamento.

  • Decorators -- Exponha a função de consulta como decorator para acesso em qualquer rota sem imports manuais.

  • Schemas -- Use JSON Schema para validar parâmetros de entrada e serializar respostas automaticamente. O Fastify usa os schemas para otimizar a serialização.

  • Timeout -- O AbortController com 5 segundos é configurado no plugin e evita consultas pendentes.

  • Rate limit local -- O @fastify/rate-limit protege o endpoint de abusos internos. A CPFHub.io não bloqueia ao atingir o limite do plano — cobra R$0,15 por consulta adicional (Gratuito: 50/mês, Pro: 1.000/mês por R$149).

  • Sensible -- O @fastify/sensible adiciona helpers como httpErrors para tratamento de erros padronizado.


Perguntas frequentes

Por que usar plugins e decorators em vez de importar a função diretamente nas rotas?

Plugins e decorators seguem o modelo de encapsulamento do Fastify, conforme descrito na documentação oficial do framework. A integração fica isolada em um único ponto, o que facilita testes unitários, substituição do provedor de CPF e reuso da lógica em múltiplas rotas sem duplicação de código.

O @fastify/rate-limit e o rate limit da CPFHub.io são a mesma coisa?

Não. O @fastify/rate-limit controla quantas requisições chegam ao seu servidor por minuto — protegendo contra abusos dos seus próprios usuários. Já a CPFHub.io controla quantas consultas sua chave de API realiza por mês. Os dois limites são independentes e complementares.

Como o plugin lida com timeout na chamada à API de CPF?

O AbortController cancela a requisição após o tempo configurado (padrão: 5 segundos). Quando isso acontece, o plugin lança um gatewayTimeout (503), informando ao cliente que o serviço externo não respondeu a tempo, sem deixar a conexão pendente no servidor.

O cache em memória (Map) é suficiente para produção?

Para volumes baixos, sim. Para aplicações com múltiplas instâncias ou alto tráfego, substitua o Map por Redis ou Memcached para compartilhar o cache entre processos. O padrão de plugin do Fastify torna essa substituição simples: basta criar um novo plugin que implemente a mesma interface get/set.


Conclusão

O Fastify com plugins e decorators oferece a melhor combinação de performance e organização para consumir a API da CPFHub.io

Cadastre-se em cpfhub.io

CPFHub.io

Pronto para integrar a API?

50 consultas gratuitas para testar agora. Sem cartão de crédito. Acesso imediato à documentação.

Redação CPFHub.io

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.

WhatsAppFale conosco via WhatsApp