# Finch: a pool-aware HTTP client for Elixir

> Finch is an Elixir HTTP client built on Mint and NimblePool, with explicit control over connection pools and per-origin configuration. It suits services that need direct control over transport behavior, while Req offers a friendlier interface when that level of control is unnecessary.

**sneako/finch** — Elixir HTTP client, focused on performance

- Repository: https://github.com/sneako/finch
- Stars: 1,365 · Forks: 151
- Language: Elixir
- License: MIT
- Published: 2026-10-16 · Updated: 2026-10-16 · Language: en
- Canonical page: https://hysenlabs.com/projects/sneako-finch

## Why Finch sits below a conventional Elixir HTTP API

Finch addresses a specific gap between low-level protocol libraries and higher-level HTTP clients. It is built on Mint and NimblePool, but it adds persistent connection pooling, a named instance that can live in an OTP supervision tree, request construction, telemetry, and configuration for individual origins. The intended users are Elixir developers who need direct control over those transport resources, especially services that make many requests to a known set of API hosts.

The repository describes Finch as performance-focused. Its stated design goals are efficient connection pooling strategies and avoiding memory copies where possible. Those are implementation goals, not a guarantee of a particular throughput or latency improvement, and the README does not publish benchmark results. Finch also positions itself below Req. It says most developers will probably prefer Req, which uses Finch's pooling while presenting a friendlier API. That distinction matters during selection: Finch is not trying to hide connection management entirely, while Req accepts that dependency and exposes a more convenient request layer.

Finch is therefore most relevant to codebases where pool size, pool count, worker selection, destination grouping, Unix sockets, or traffic separation are application concerns. It is a poorer fit when the team mainly wants a short, high-level request function and has little reason to tune connection management.

## How Finch groups requests by origin and protocol

For HTTP/1, Finch parses a request URL into a `{scheme, host, port}` tuple. It then maintains one or more pools associated with that destination. A request to an unconfigured destination causes Finch to create a pool from the `:default` configuration the first time that destination is used. This makes the API convenient for open-ended traffic while still permitting known origins to receive predetermined pool settings.

Pool configuration has two dimensions. The README uses `:size` for the size assigned to a pool and `:count` for the number of pool processes started for each configured origin. In the documented example, `:default` is `[size: 10, count: 2]`, so each newly encountered origin receives two pool processes of the configured size. The README points to `Finch.start_link/1` for the interaction between these settings and for worker selection when `:count` is greater than one.

The important operational consequence is multiplication. A configuration applied across ten distinct `{scheme, host, port}` combinations starts twenty pool processes when `:count` is 2. Pool settings should therefore be calculated against expected origins, connection limits, and application concurrency rather than copied from the example without adjustment.

HTTP/1 pools can also expire after inactivity when `pool_max_idle_time` is configured. The README explicitly limits that option to HTTP1 pools. It does not say that Finch terminates every dynamically created pool automatically by default.

## Installing Finch and issuing the first supervised request

The README gives an installation route through Hex rather than a Git clone procedure. Add the documented dependency to `mix.exs`. The version requirement is not pinned to an exact release, so dependency resolution and `mix.lock` remain part of the installation process.

```elixir
def deps do
  [
    {:finch, "~> 0.24"}
  ]
end
```

After adding the dependency, Finch must be started and assigned a `:name`. The README recommends placing it in an OTP supervision tree. This is more significant than calling a startup function once, because the named Finch instance is then represented as a supervised child.

```elixir
children = [
  {Finch, name: MyFinch}
]
```

Once the instance is running, build a request and pass the instance name to `Finch.request/2`. This first request targets `hex.pm` and uses the default pool configuration unless a more specific configuration was declared.

```elixir
Finch.build(:get, "https://hex.pm") |> Finch.request(MyFinch)
```

The README also documents dynamic startup for the uncommon case where supervision-tree registration is not appropriate.

```elixir
Finch.start_link(name: MyFinch)
```

Dynamic startup does not provide the same declaration as a child specification in the surrounding supervision tree. The choice therefore depends on how the application owns its processes.

## Preconfiguring HTTP/1 pools for known endpoints

Known URLs can be assigned their own pool settings under the `:pools` key. The repository presents separate configurations for the default and for Hex. Preconfiguration means Finch parses the URLs at startup and starts the corresponding pools immediately, rather than waiting for the first request to each origin.

```elixir
children = [
  {Finch,
   name: MyConfiguredFinch,
   pools: %{
     :default => [size: 10, count: 2],
     "https://hex.pm" => [size: 32, count: 8]
   }}
]
```

This is useful when an endpoint has a known concurrency profile and the application does not want first-request pool creation to determine its initial behavior. It also creates an explicit budgeting task. The example gives the Hex origin a much larger configuration than the default, and applying it to many origins would multiply the resulting process count.

Pool sizing is not the only decision. A service with many independent hosts may spread requests across a large number of origin keys, each receiving `:count` pools. A service making repeated calls to one origin has a different allocation pattern. The README does not provide a capacity planner, so application concurrency, proxy behavior, downstream limits, and memory budgets must be evaluated separately.

The configuration is tied to Finch's HTTP/1 origin model. It does not mean that every URL receives a process: Finch parses each URL into scheme, host, and port before associating it with a pool. That behavior should be considered when URLs differ only in paths or query strings, since those components are not part of the documented pool key.

## Using tags to isolate traffic for the same destination

Pool tags allow Finch to maintain separate pools for the same `{scheme, host, port}` combination. The README lists API traffic, web traffic, tenants, and JWT-based separation as possible reasons. This is more than a request label: it changes which pool configuration can serve the request.

`Finch.Pool.new/2` builds a destination key with an optional tag. The documented configuration assigns different sizes to tagged pools for one API and for a Unix socket, while retaining a default entry.

```elixir
children = [
  {Finch,
   name: MyTaggedFinch,
   pools: %{
     Finch.Pool.new("https://api.example.com") => [size: 50, count: 4],
     Finch.Pool.new("https://api.example.com", tag: :web) => [size: 20, count: 2],
     Finch.Pool.new("http+unix:///tmp/api.sock", tag: :api) => [size: 30, count: 2],
     Finch.Pool.new("http+unix:///tmp/api.sock", tag: :web) => [size: 10, count: 1],
     :default => [size: 10, count: 1]
   }}
]
```

A request selects the `:api` pool through `pool_tag`.

```elixir
request = Finch.build(:get, "https://api.example.com/users", [], nil, pool_tag: :api)
Finch.request(request, MyTaggedFinch)
```

The fallback rule requires attention. The README states that if a requested tag does not exist, the request uses the `:default` configuration. A missing tag does not necessarily fail immediately. Teams that depend on strict isolation must therefore verify that every expected tagged pool exists. `Finch.find_pool/2` can be used for explicit existence checks.

```elixir
case Finch.find_pool(MyFinch, Finch.Pool.new("https://api.internal", tag: :api)) do
  {:ok, _pid} -> # Pool exists
  :error -> # Pool not found
end
```

This fallback preserves operation for ordinary requests, but it can conceal a configuration mismatch.

## User-managed pools and Unix socket routing

Finch allows pools to be placed directly under an application's supervision tree. The Finch instance must already be started. The documented child specifications connect each pool to `MyFinch` and identify its destination through `Finch.Pool.new/2`.

```elixir
children = [
  {Finch, name: MyFinch},
  {Finch.Pool, finch: MyFinch, pool: Finch.Pool.new("https://api.internal", tag: :api), size: 10},
  {Finch.Pool, finch: MyFinch, pool: Finch.Pool.new("https://node-2.internal", tag: :node2), size: 10}
]
Supervisor.start_link(children, strategy: :one_for_one)
```

This differs from pools started by `Finch.start_pool/3`, which are added under Finch's internal supervisor. The README states that user-managed pools integrate with request submission, stopping, status inspection, and lookup. Moving pool ownership into the application can make its lifecycle visible in the application supervision tree, but it also makes ordering explicit: Finch must exist before its pools.

Unix socket requests use the `http+unix` URL form for pool identity and the `unix_socket` request option for routing. The following request selects the `:api` tagged socket pool.

```elixir
request =
  Finch.build(
    :get,
    "http://localhost/",
    [],
    nil,
    unix_socket: "/tmp/api.sock",
    pool_tag: :api
  )
Finch.request(request, MyTaggedFinch)
```

Socket permissions, retry behavior, cleanup of stale Unix sockets, and failure handling should be checked in the Finch and Mint documentation before adopting this path in production.

## Finch compared with Req and direct Mint use

The clearest documented alternative is Req. The README says Req takes advantage of Finch's pooling and offers a friendly API. In practical terms, that means an application choosing Req can keep Finch's pool-oriented transport foundation while using a higher-level request interface. Finch is the appropriate choice when direct access to Finch's pool tags, pool sizing, user-managed pools, or request construction is part of the design.

Mint is the other relevant comparison because it is one of Finch's foundations. Finch adds pooling and instance management over Mint. A team that needs Mint's direct protocol API but does not want Finch's instance and pooling layer could work directly with Mint, although the README does not document that as a migration procedure or claim equivalent behavior. It only identifies Mint as the underlying project.

Finch has a real operational cost. Each origin can introduce multiple pool processes, tagged pools add another allocation dimension, and user-managed pools add supervision entries. Dynamic pools can also remain active unless a supported idle-time option is configured for HTTP1. A small application with infrequent traffic may have more configuration work than it needs. A high-concurrency service with predictable origins can use the documented settings and tagging model to make resource ownership explicit, but it must still size each combination deliberately.

The MIT license permits use and modification under that license's terms, including distribution and sublicensing subject to its conditions. This is not legal advice. The repository has no GitHub releases, so upgrade planning cannot be based on a release page. The README's `~> 0.24` constraint and the repository's `CHANGELOG.md` are the available version references.

## Maintenance, observability, and upgrade boundaries

The repository is not archived, and the last push was on 2026-09-30. That status does not by itself establish release cadence or API stability, particularly because the repository has no GitHub releases. Maintenance signals should be read narrowly. The presence of CI configuration in the repository layout and documentation for telemetry and SSL key logging show that operational facilities are part of the project, but the README does not state a support policy for specific versions.

Telemetry is available through `Finch.Telemetry`, which the README names as the place for event details. 

TLS secret logging can write session secrets to a file for later use in tools such as Wireshark. With TLS 1.3, the README says `keep_secrets: true` must also be added to `:transport_opts`. The destination can be set with the `:ssl_key_log_file` connection option or through the `SSLKEYLOGFILE` environment variable. This feature exposes sensitive TLS material to the selected file and should be limited to controlled diagnostic environments. Finch is an ordinary HTTP client, not a secrets-redaction or audit-log product.

Upgrade cost is therefore tied to dependency review, pool configuration, and behavioral verification. The repository includes `CHANGELOG.md` and `mix.lock`. Before changing the Finch dependency, an application should inspect those files and retest origin routing, pool creation, tags, Unix sockets, and any TLS options used by its deployment.

## Conclusion

Application teams that need explicit pool sizing, origin isolation, Unix socket support, or direct control over a Mint-based request flow have a concrete reason to adopt Finch. Projects that want a higher-level client with a friendlier API should examine Req first. Before adoption, verify the HTTP protocol requirements for each destination, calculate the process and connection cost of the intended pool settings, and confirm that the selected Finch version fits the application's Elixir and dependency constraints.

## FAQ

### How do I install Finch in an Elixir project?

Add `{:finch, "~> 0.24"}` to the dependency list in `mix.exs`, as shown in the README.

### How do I start Finch and make an HTTP request?

Start Finch with a `:name`, preferably as `{Finch, name: MyFinch}` in a supervision tree, then use `Finch.build/2` and `Finch.request/2`. The first documented request targets `https://hex.pm`.

### Can I configure different pool sizes for different URLs in Finch?

Yes. The `:pools` option accepts URL keys and a `:default` entry, each with settings such as `size` and `count`. For HTTP/1, Finch associates pools with the URL's `{scheme, host, port}` tuple.

### What happens when a Finch pool tag does not exist?

According to the README, a request specifying a missing `:pool_tag` uses the `:default` configuration. Teams that require a specific tagged pool can check it with `Finch.find_pool/2`.

### Does Finch support Unix socket requests?

The README documents `http+unix` pool URLs and the `unix_socket` request option, together with tagged pool selection.

## Sources

- [Issues](https://github.com/sneako/finch/issues)
- [License: MIT](https://github.com/sneako/finch/blob/main/LICENSE)
- [README](https://github.com/sneako/finch/blob/main/README.md)
- [sneako/finch on GitHub](https://github.com/sneako/finch)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/sneako-finch
