Open-source project
wojtekmach/req avatar
wojtekmach/req

Req: an HTTP client for Elixir built out of steps

Req is a batteries-included HTTP client for Elixir.

1,335 stars177 forksElixirApache-2.0

At a glance

What is it?
Req is a batteries-included HTTP client for Elixir where nearly every behaviour, decoding, redirects, retries, authentication and compression, is a separate step you can reorder, replace or write yourself. It suits an Elixir service that wants a real client without a rigid pipeline, and the step model is the part that distinguishes it.
Who is it for?
Adopt Req if you write Elixir and want an HTTP client where the retry, redirect, decode and authentication behaviour is visible and adjustable rather than buried. Skip it if you want a client with a single fixed configuration, since the step model is a layer of indirection you do not need.
Can I use it commercially?
Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 4 days ago.
What is it written in?
Mainly Elixir, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

What you get in a couple of lines

Req is a batteries-included HTTP client for Elixir, and the README opens with the shortest possible demonstration:

elixir
Mix.install([
  {:req, "~> 0.8.0-rc"}
])

Req.get!("https://api.github.com/repos/wojtekmach/req").body["description"]
#=> "Req is a batteries-included HTTP client for Elixir."

That one line produces automatic response body decoding, following redirects, retrying on errors and much more. The primary language is Elixir, the licence is Apache-2.0, the homepage is hexdocs.pm/req, the last push was on 2026-10-09, and the releases are v0.7.5 on 2026-10-06, v0.7.4 on 2026-08-26 and v0.7.3 on 2026-08-19. Note the version in the example is a release candidate for the 0.8 line while the published stable is 0.7.5, which is the usual state of an Elixir library where the next major lands early.

Steps, and why everything is one

The design decision that defines Req is stated twice and is worth repeating. Virtually all of the features are broken down into individual pieces called steps, and you can easily reuse and rearrange built-in steps and write new ones. So a request in Req is a pipeline of named steps, and the order of that pipeline is the configuration. Automatic response body decoding, following redirects, retrying on errors, compressing the request body and decompressing the response are all steps, not hardcoded branches in a client struct. The consequence is that a behaviour you want to change is a step you replace or a step you insert, and a behaviour the library did not anticipate is a step you write. The high-level API is `Req.request/1`, `Req.stream/4`, `Req.new/1` and the bang variants like `Req.get!/2` and `Req.post!/2`, so the common case stays one line.

The feature list, grouped by what it does to the request

The features fall into a few clear groups. On the request side there is request body compression, request body encoding supporting urlencoded and multipart forms and JSON, encoding params as a query string, setting a base URL, templated request paths, range requests, and AWS V4 signature support. On the response side there is automatic response body decoding, opt-in response body decompression supporting gzip, brotli and zstd, and verifying the response body against a checksum. Around the request there is authentication with Basic, Digest, Bearer and .netrc, following redirects, retries on errors, and raising on an unexpected response status. And the transport layer has request body streaming by setting `body: enumerable` or `body: fun`, and response body streaming via `Req.stream/4` or by setting `into: collectable` or `into: :self`.

Streaming, in both directions

The streaming examples are worth looking at, because they show the two directions the library treats differently. A request body can be a stream, and the example passes a `Stream.duplicate("foo", 3)` with a text/plain content type, and the server echoes back `foofoofoo`. A response body can be streamed either through a callback function or into a Collectable, and the callback example decodes each event with `JSON.decode!` and prints a type and title, running against the Wikimedia recent-changes stream. The Collectable form is shorter, passing `into: IO.stream()` and getting an `%IO.Stream{}` as the response body, with the status still available on the response. The distinction between `into: collectable` and `into::self` is the one to check in the docs, because the two hand the body back differently.

Reusing a request struct, and adding your own step

When several requests share options, the library suggests building up a request struct and reusing it:

elixir
req = Req.new(base_url: "https://api.github.com")

Req.get!(req, url: "/repos/sneako/finch").body["description"]
#=> "Elixir HTTP client, focused on performance"

The step mechanism then lets you insert your own behaviour. The example appends a request step that inspects the URL just before the request is made:

elixir
req =
  Req.new(base_url: "https://api.github.com")
  |> Req.Request.append_request_steps(
    debug_url: fn request ->
      IO.inspect(URI.to_string(request.url))
      request
    end
  )

Custom steps can also be packaged into plugins so that they are easier for others to use, and the README points at a related packages section. The plugin examples in the documentation show what that buys, with `ReqEasyHTML.attach()` for parsing HTML, `ReqS3.attach()` for fetching from S3 URLs, `ReqHex.attach()` for Hex package tarballs and `ReqGitHubOAuth.attach()` for the device-code login flow.

Testing support, and the adapter underneath

Two features matter if you are writing a service rather than a script. Plug-based HTTP mocks and stubs through `Req.Test` mean you can test your HTTP calls without a network, which for an Elixir service is the difference between a test suite that runs and one that does not. And running against a plug through `Req.Plug` means the same code path works when your application is behind a plug. The transport itself is pluggable, with adapters, and by default Req uses the Finch adapter. That last detail is a real design choice rather than an implementation detail, because Finch is itself an Elixir HTTP client focused on performance, and choosing an adapter means you can move to a different transport without touching the step pipeline. The repository also carries a `config/` directory, `examples/` with `openai.livemd` and `reverse_proxy.livemd`, and a `mix.exs` and `mix.lock`.

The v0.8 internals change, and what it means for plugins

There is a note in the README about new internals in Req v0.8, and it is the one upgrade warning worth reading. Req v0.8 contains revamped internals. Most end-users should be unaffected, but if you are using custom steps or plugins, response and error steps are now deprecated in favour of step wrappers, with a link to the Steps and Step Wrappers section of the `Req.Request` module documentation. The distinction is a reasonable one, since a step wrapper can act on the request and the response together where a plain request step could only see the request, but it does mean a plugin written against 0.7 needs attention. The rest of the surface is stable, and the changelog at the repository root tracks the rest of the history.

Editorial conclusion

Adopt Req if you write Elixir and want an HTTP client where the retry, redirect, decode and authentication behaviour is visible and adjustable rather than buried. Skip it if you want a client with a single fixed configuration, since the step model is a layer of indirection you do not need. Before you upgrade, note that Req v0.8 revamped the internals and deprecated response and error steps in favour of step wrappers, which affects any custom step or plugin you have written.

Frequently asked questions

What is Req?

A batteries-included HTTP client for Elixir. Virtually all of its features are broken down into individual steps you can reuse, rearrange or write yourself, covering response decoding, redirects, retries, authentication, compression and streaming.

How do I install Req in a script?

With `Mix.install([{:req, "~> 0.8.0-rc"}])`, which needs no project. The README's example then calls `Req.get!("https://api.github.com/repos/wojtekmach/req").body["description"]`.

How do I add my own behaviour to a Req request?

By appending a request step to the request struct, for example with Req.Request.append_request_steps, which takes a named step and a function that receives and returns the request. Custom steps can also be packaged into plugins so other people can use them.

What changed in Req v0.8?

The internals were revamped. Most end-users are unaffected, but if you use custom steps or plugins the response and error steps are deprecated in favour of step wrappers, and the Req.Request module documentation has a Steps and Step Wrappers section covering the change.

Official sources

  1. License: Apache-2.0
  2. Project website
  3. README
  4. Releases
  5. wojtekmach/req on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/wojtekmach-req.svg)](https://hysenlabs.com/projects/wojtekmach-req)

Was this useful?