# Como integrar validação de CPF em F# com Giraffe e Suave

> Aprenda a integrar validação de CPF em F# usando Giraffe sobre ASP.NET Core e Suave para consumir a API da CPFHub.io.

**Publicado:** 21/07/2026
**Autor:** Redação CPFHub.io
**URL:** https://cpfhub.io/blog/como-integrar-validacao-de-cpf-em-fsharp-com-giraffe-e-suave

---


O **F#** é uma linguagem funcional-first que roda no ecossistema .NET, com discriminated unions e composição de funções que tornam o tratamento de erros seguro e expressivo. Para integrar validação de CPF em F#, a API da CPFHub.io aceita `GET https://api.cpfhub.io/cpf/{CPF}` autenticado pelo header `x-api-key` e responde em ~900ms com nome, data de nascimento e status do documento. O **Giraffe** funciona como middleware sobre ASP.NET Core para exposição da rota; o **Suave** oferece uma alternativa funcional mais leve e independente. Consulte a [documentação oficial do F#](https://fsharp.org/docs) para configurar o .NET SDK antes de começar.

---

## 1. Pré-requisitos

* **.NET 8.0+ SDK** instalado.

* Um projeto F# criado: `dotnet new console -lang F# -n CpfValidatorFSharp`.

* Uma conta gratuita na [**CPFHub.io**](https://www.cpfhub.io/)

---

## 2. Adicione as dependências

```bash
dotnet add package Giraffe
dotnet add package FSharp.Data
dotnet add package Newtonsoft.Json
```

---

## 3. Defina os tipos de dados

Use discriminated unions para modelar resultados e erros:

```fsharp
// Types.fs
module CpfValidator.Types

open System.Text.Json.Serialization

type CpfData =
 { [<JsonPropertyName("cpf")>] Cpf: string
 [<JsonPropertyName("name")>] Name: string
 [<JsonPropertyName("nameUpper")>] NameUpper: string
 [<JsonPropertyName("gender")>] Gender: string
 [<JsonPropertyName("birthDate")>] BirthDate: string
 [<JsonPropertyName("day")>] Day: int
 [<JsonPropertyName("month")>] Month: int
 [<JsonPropertyName("year")>] Year: int }

type CpfApiResponse =
 { [<JsonPropertyName("success")>] Success: bool
 [<JsonPropertyName("data")>] Data: CpfData option }

type CpfError =
 | InvalidCpf of string
 | NotFound of string
 | Unauthorized of string
 | RateLimited of string
 | TimeoutError of string
 | ApiError of string * int

type CpfResult = Result<CpfData, CpfError>
```

---

## 4. Implemente o cliente da API

Crie o módulo de consulta usando `HttpClient`:

```fsharp
// CpfHubClient.fs
module CpfValidator.CpfHubClient

open System
open System.Net.Http
open System.Net.Http.Json
open System.Text.RegularExpressions
open System.Threading
open CpfValidator.Types

type CpfHubConfig =
 { ApiKey: string
 BaseUrl: string
 TimeoutMs: int }

let defaultConfig =
 { ApiKey = "SUA_CHAVE_DE_API"
 BaseUrl = "https://api.cpfhub.io"
 TimeoutMs = 5000 }

let private limparCpf (cpf: string) =
 Regex.Replace(cpf, @"\D", "")

let private mapearErro (statusCode: int) =
 match statusCode with
 | 400 -> InvalidCpf "CPF com formato inválido"
 | 401 -> Unauthorized "Chave de API inválida ou ausente"
 | 404 -> NotFound "CPF não encontrado na base de dados"
 // Nota: a CPFHub.io não retorna 429; ao exceder o plano gratuito,
 // cobra R$0,15 por consulta adicional sem bloquear requisições.
 | code -> ApiError ($"Erro HTTP {code}", code)

let consultarCpf (config: CpfHubConfig) (httpClient: HttpClient) (cpf: string) : Async<CpfResult> =
 async {
 let cpfLimpo = limparCpf cpf

 if cpfLimpo.Length <> 11 then
 return Error (InvalidCpf "CPF deve conter exatamente 11 dígitos")
 else
 let url = $"{config.BaseUrl}/cpf/{cpfLimpo}"

 use cts = new CancellationTokenSource(config.TimeoutMs)

 try
 use request = new HttpRequestMessage(HttpMethod.Get, url)
 request.Headers.Add("x-api-key", config.ApiKey)
 request.Headers.Add("Accept", "application/json")

 let! response =
 httpClient.SendAsync(request, cts.Token)
 |> Async.AwaitTask

 if response.IsSuccessStatusCode then
 let! resultado =
 response.Content.ReadFromJsonAsync<CpfApiResponse>(cancellationToken = cts.Token)
 |> Async.AwaitTask

 match resultado with
 | null -> return Error (ApiError ("Resposta nula da API", 500))
 | r when r.Success ->
 match r.Data with
 | Some data -> return Ok data
 | None -> return Error (ApiError ("Resposta sem dados", 500))
 | _ -> return Error (ApiError ("Consulta sem sucesso", 500))
 else
 return Error (mapearErro (int response.StatusCode))

 with
 | :? OperationCanceledException ->
 return Error (TimeoutError "Timeout ao consultar a API da CPFHub")
 | :? HttpRequestException as ex ->
 return Error (ApiError ($"Erro de conexão: {ex.Message}", 502))
 | ex ->
 return Error (ApiError ($"Erro inesperado: {ex.Message}", 500))
 }
```

---

## 5. Crie o servidor com Giraffe

Implemente a API REST usando Giraffe sobre ASP.NET Core:

```fsharp
// Program.fs (versão Giraffe)
module CpfValidator.Program

open System
open System.Net.Http
open Microsoft.AspNetCore.Builder
open Microsoft.AspNetCore.Hosting
open Microsoft.Extensions.DependencyInjection
open Microsoft.Extensions.Hosting
open Giraffe
open CpfValidator.Types
open CpfValidator.CpfHubClient

let private errorToStatus (error: CpfError) =
 match error with
 | InvalidCpf _ -> 400
 | NotFound _ -> 404
 | Unauthorized _ -> 401
 | RateLimited _ -> 429
 | TimeoutError _ -> 504
 | ApiError (_, code) -> code

let private errorToMessage (error: CpfError) =
 match error with
 | InvalidCpf msg -> msg
 | NotFound msg -> msg
 | Unauthorized msg -> msg
 | RateLimited msg -> msg
 | TimeoutError msg -> msg
 | ApiError (msg, _) -> msg

let consultarCpfHandler (cpf: string) : HttpHandler =
 fun next ctx ->
 task {
 let httpClient = ctx.GetService<IHttpClientFactory>().CreateClient()
 let config = defaultConfig

 let! resultado = consultarCpf config httpClient cpf |> Async.StartAsTask

 match resultado with
 | Ok data ->
 return! json {| success = true; data = data |} next ctx
 | Error error ->
 ctx.SetStatusCode(errorToStatus error)
 return! json {| success = false; error = errorToMessage error |} next ctx
 }

let webApp : HttpHandler =
 choose [
 GET >=> routef "/api/cpf/%s" consultarCpfHandler
 RequestErrors.NOT_FOUND "Recurso não encontrado"
 ]

let configureApp (app: IApplicationBuilder) =
 app.UseGiraffe webApp

let configureServices (services: IServiceCollection) =
 services.AddGiraffe() |> ignore
 services.AddHttpClient() |> ignore

[<EntryPoint>]
let main args =
 Host.CreateDefaultBuilder(args)
 .ConfigureWebHostDefaults(fun webHost ->
 webHost
 .Configure(configureApp)
 .ConfigureServices(configureServices)
 .UseUrls("http://0.0.0.0:5000")
 |> ignore)
 .Build()
 .Run()
 0
```

---

## 6. Versão com Suave

Alternativamente, use o Suave para um servidor mais leve e funcional:

```fsharp
// ProgramSuave.fs
module CpfValidator.ProgramSuave

open Suave
open Suave.Filters
open Suave.Operators
open Suave.Successful
open Suave.RequestErrors
open System.Net.Http
open Newtonsoft.Json
open CpfValidator.Types
open CpfValidator.CpfHubClient

let private httpClient = new HttpClient()

let consultarCpfSuave (cpf: string) : WebPart =
 fun ctx ->
 async {
 let config = defaultConfig
 let! resultado = consultarCpf config httpClient cpf

 let jsonResponse =
 match resultado with
 | Ok data ->
 let body = JsonConvert.SerializeObject({| success = true; data = data |})
 OK body
 | Error error ->
 let msg = errorToMessage error
 let body = JsonConvert.SerializeObject({| success = false; error = msg |})
 BAD_REQUEST body

 return! jsonResponse ctx
 }

let app : WebPart =
 choose [
 GET >=> pathScan "/api/cpf/%s" consultarCpfSuave
 NOT_FOUND "Recurso não encontrado"
 ]

let main () =
 startWebServer defaultConfig app
```

---

## 7. Teste a integração

```bash
dotnet run
```

```bash
curl -X GET http://localhost:5000/api/cpf/12345678900
```

Resposta esperada:

```json
{
 "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
 }
}
```

---

## 8. Boas práticas

* **Discriminated Unions** -- Use DUs para modelar todos os estados possíveis de uma operação, incluindo erros. O compilador garante tratamento exaustivo.

* **Result** -- Use `Result<'T, 'E>` para operações que podem falhar, evitando exceções no fluxo principal.

* **Async workflows** -- Use `async {}` para operações assíncronas, mantendo o código funcional e composável.

* **Timeout** -- Configure timeout via `CancellationTokenSource` para garantir que requisições não travem, respeitando a latência de ~900ms da API.

* **HttpClientFactory** -- No Giraffe, use `IHttpClientFactory` para gerenciar o ciclo de vida do `HttpClient`.

* **LGPD** -- A API da CPFHub.io é 100% compatível com a LGPD. Garanta conformidade no tratamento de dados pessoais em sua aplicação F#.

---

## Perguntas frequentes

### Como configurar autenticação ao consumir a API de CPF em F#?

A autenticação usa o header `x-api-key` em cada requisição. No `HttpRequestMessage`, adicione `request.Headers.Add("x-api-key", config.ApiKey)` antes de enviar. A chave de API é gerada no painel da CPFHub.io e deve ser lida de uma variável de ambiente ou secret manager — nunca embutida no código-fonte.

### Qual a diferença entre usar Giraffe e Suave para expor a rota de consulta de CPF?

O Giraffe funciona como middleware sobre o ASP.NET Core, aproveitando toda a infraestrutura .NET (injeção de dependência, `IHttpClientFactory`, logging estruturado). O Suave é um servidor web autônomo, mais leve e sem dependências do ASP.NET, adequado para serviços simples ou ambientes com restrição de footprint. Para produção com múltiplos endpoints, Giraffe tende a ser mais fácil de operar.

### O que acontece quando o limite de consultas mensais é atingido?

A CPFHub.io não bloqueia requisições ao atingir o limite do plano. O plano gratuito inclui 50 consultas mensais; acima disso, cada consulta adicional é cobrada a R$0,15 automaticamente. O plano Pro oferece 1.000 consultas por R$149/mês com o mesmo modelo de excedente proporcional.

### Como o sistema de tipos do F# ajuda no tratamento de erros da integração?

O discriminated union `CpfError` força que todo ponto de uso trate explicitamente cada caso de falha — `InvalidCpf`, `NotFound`, `Unauthorized`, `TimeoutError` e `ApiError`. O compilador do F# gera aviso para pattern matching incompleto, eliminando a possibilidade de estados de erro não tratados chegarem ao cliente.

---

### Leia também

- [Como validar CPF no frontend com React e API REST](https://cpfhub.io/blog/como-validar-cpf-no-frontend-com-react-e-api-rest)
- [Autenticação em APIs REST: como garantir segurança na consulta de CPF](https://cpfhub.io/blog/autenticacao-apis-rest-seguranca-consulta-cpf)
- [Como consumir API de CPF em Haskell com Servant e wreq](https://cpfhub.io/blog/como-consumir-api-de-cpf-em-haskell-com-servant-e-wreq)
- [Como criar um SDK interno para padronizar consultas de CPF na empresa](https://cpfhub.io/blog/como-criar-sdk-interno-padronizar-consultas-cpf-empresa)

---

## Conclusão

Integrar a API da [**CPFHub.io**](https://www.cpfhub.io/)

Cadastre-se em [cpfhub.io](https://www.cpfhub.io/)

