# Client

In Tesla, a **client** is an entity that combines middleware and an adapter,
created using `Tesla.client/2`. Middleware components modify or enhance requests
and responses—such as adding headers or handling authentication—while adapters
handle the underlying HTTP communication. For more details, see the sections on
[middleware](./2.middleware.md) and [adapters](./3.adapter.md).

## Creating a Client

A client is created using `Tesla.client/2`, which takes a list of middleware
and an adapter.

```elixir
client = Tesla.client([Tesla.Middleware.PathParams, Tesla.Middleware.Logger])
```

You can then use the client to make requests:

```elixir
Tesla.get(client, "/users/123")
```

### Passing Options to Middleware

You can pass options to middleware by registering the middleware as a tuple
of two elements, where the first element is the middleware module and the
second is the options.

```elixir
client = Tesla.client(
  [{Tesla.Middleware.BaseUrl, "https://api.example.com"}]
)
```

### Passing Adapter

By default, the global adapter is used. You can override this by passing an
adapter to the client.

```elixir
client = Tesla.client([], Tesla.Adapter.Mint)
```
You can also pass options to the adapter.

```elixir
client = Tesla.client([], {Tesla.Adapter.Mint, pool: :my_pool})
```

## Single-Client (Singleton) Pattern

A common approach in applications is to encapsulate client creation within a
module or function that sets up standard middleware and adapter configurations.
This results in a single, shared client instance used throughout the
application. For example:

```elixir
defmodule MyApp.ServiceName do
  defp client do
    middleware = [
      {Tesla.Middleware.BaseUrl, "https://api.service.com"},
      {Tesla.Middleware.BearerAuth, token: bearer_token()},
      # Additional middleware...
    ]
    Tesla.client(middleware, adapter())
  end

  defp adapter do
    Keyword.get(config(), :adapter)
  end

  defp bearer_token do
    Keyword.fetch!(config(), :bearer_token)
  end

  defp config do
    Application.get_env(:my_app, __MODULE__, [])
  end
end
```

In this pattern, the client is constructed internally, and operations use this
singleton client:

```elixir
defmodule MyApp.ServiceName do
  def operation_name(body) do
    url = "/endpoint"
    # The client() function is called internally
    response = Tesla.post!(client(), url, body)
    # Process the response...
  end

  defp client do
    # Client construction as shown earlier
  end
end
```

You can then use the module to make requests without managing the client externally:

```elixir
{:ok, response} = MyApp.ServiceName.operation_name(%{key: "value"})
```

## Multi-Client Pattern

In scenarios where different configurations are needed—such as multi-tenancy
applications or interacting with multiple services—you can modify the client
function to accept configuration parameters. This allows for the creation of
multiple clients with varying settings:

```elixir
defmodule MyApp.ServiceName do
  def operation_name(client, body) do
    url = "/endpoint"
    # The client is passed as a parameter
    response = Tesla.post!(client, url, body)
    # Process the response...
  end

  def client(opts) do
    middleware = [
      {Tesla.Middleware.BaseUrl, opts[:base_url]},
      {Tesla.Middleware.BearerAuth, token: opts[:bearer_token]},
      # Additional middleware...
    ]
    Tesla.client(middleware, opts[:adapter])
  end
end
```

Now, you can create clients with different configurations:

```elixir
client = MyApp.ServiceName.client(
  base_url: "https://api.service.com",
  bearer_token: "token_value",
  adapter: Tesla.Adapter.Hackney
  # Additional options...
)
{:ok, response} = MyApp.ServiceName.operation_name(client, %{key: "value"})
```

## Comparing Single-Client and Multi-Client Patterns

The choice between using a single-client (singleton) or multi-client pattern
depends on your specific needs:

- **Library Authors**: It's generally advisable to avoid the singleton client
  pattern. Hardcoding configurations can limit flexibility and hinder users in
  multi-tenant environments. Providing the ability to create clients with custom
  configurations makes your library more adaptable and user-friendly.

- **Application Developers**: For simpler applications, a singleton client might
  suffice initially. However, adopting the multi-client approach from the outset
  can prevent future refactoring if your application grows or needs change.

Understanding these patterns helps you design applications and libraries that
are flexible and maintainable, aligning with best practices in software
development.
