> For the complete documentation index, see [llms.txt](https://hadrien-lejard.gitbook.io/chopper/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://hadrien-lejard.gitbook.io/chopper/requests.md).

# Requests

## Available Request annotations

| Annotation                                 | HTTP verb | Description                                             |
| ------------------------------------------ | --------- | ------------------------------------------------------- |
| `@GET()`, `@get`                           | `GET`     | Defines a `GET` request.                                |
| `@POST()`, `@post`                         | `POST`    | Defines a `POST` request.                               |
| `@PUT()`, `@put`                           | `PUT`     | Defines a `PUT` request.                                |
| `@PATCH()`, `@patch`                       | `PATCH`   | Defines a `PATCH` request.                              |
| `@DELETE()`, `@delete`                     | `DELETE`  | Defines a `DELETE` request.                             |
| `@HEAD()`, `@head`                         | `HEAD`    | Defines a `HEAD` request.                               |
| `@OPTIONS()`, `@options`                   | `OPTIONS` | Defines an `OPTIONS` request.                           |
| `@Path()`, `@path`                         | -         | Defines a dynamic path parameter.                       |
| `@Body()`, `@body`                         | -         | Defines the request's body.                             |
| `@Header()`, `@header`                     | -         | Defines a dynamic request header.                       |
| `@FormUrlEncoded()`, `@formUrlEncoded`     | -         | Defines an `application/x-www-form-urlencoded` request. |
| `@Multipart()`, `@multipart`               | -         | Defines a `multipart/form-data` request.                |
| `@Query()`, `@query`                       | -         | Defines a query parameter.                              |
| `@QueryMap()`, `@queryMap`                 | -         | Defines a query parameter map.                          |
| `@FactoryConverter()`, `@factoryConverter` | -         | Defines a request/response converter factory.           |
| `@Field()`, `@field`                       | -         | Defines a form field.                                   |
| `@FieldMap()`, `@fieldMap`                 | -         | Defines a form field map.                               |
| `@Part()`, `@part`                         | -         | Defines a multipart part.                               |
| `@PartMap()`, `@partMap`                   | -         | Defines a multipart part map.                           |
| `@PartFile()`, `@partFile`                 | -         | Defines a multipart file part.                          |
| `@PartFileMap()`, `@partFileMap`           | -         | Defines a multipart file part map.                      |
| `@Tag()`, `@tag`                           | -         | Defines a tag parameter.                                |
| `@AbortTrigger()`, `@abortTrigger`         | -         | Defines a request cancellation trigger.                 |

## Path resolution

Chopper handles paths passed to HTTP verb annotations' `path` parameter based on the path's content.

If the `path` value is a relative path, it will be concatenated to the URL composed of the `baseUrl` of the `ChopperClient` and the `baseUrl` of the enclosing service class (provided as a parameter of the `@ChopperApi` annotation).

Here are a few examples of the described behavior:

| Variable   | URI                           |
| ---------- | ----------------------------- |
| base URL   | <https://example.com/>        |
| Path       | profile                       |
| **Result** | <https://example.com/profile> |

| Variable         | URI                                 |
| ---------------- | ----------------------------------- |
| base URL         | <https://example.com/>              |
| Service base URL | profile                             |
| Path             | /image                              |
| **Result**       | <https://example.com/profile/image> |

| Variable         | URI                                 |
| ---------------- | ----------------------------------- |
| base URL         | <https://example.com/>              |
| Service base URL | profile                             |
| Path             | image                               |
| **Result**       | <https://example.com/profile/image> |

> Chopper detects and handles missing slash (`/`) characters on URL segment borders, but *does not* handle duplicate slashes.

If the service's `baseUrl` concatenated with the request's `path` results in a full URL, the `ChopperClient`'s `baseUrl` is ignored.

| Variable         | URI                           |
| ---------------- | ----------------------------- |
| base URL         | <https://example.com/>        |
| Service base URL | <https://api.github.com/>     |
| Path             | user                          |
| **Result**       | <https://api.github.com/user> |

A `path` containing a full URL replaces the base URLs of both the `ChopperClient` and the service class entirely for a request.

| Variable   | URI                           |
| ---------- | ----------------------------- |
| base URL   | <https://example.com/>        |
| Path       | <https://api.github.com/user> |
| **Result** | <https://api.github.com/user> |

| Variable         | URI                           |
| ---------------- | ----------------------------- |
| base URL         | <https://example.com/>        |
| Service base URL | profile                       |
| Path             | <https://api.github.com/user> |
| **Result**       | <https://api.github.com/user> |

## Path parameters

Dynamic path parameters can be defined in the URL with replacement blocks. A replacement block is an alphanumeric substring of the path surrounded by `{` and `}`. In the following example `{id}` is a replacement block.

```dart
@GET(path: "/{id}")
```

Use the `@Path()` annotation to bind a parameter to a replacement block. This way the parameter's name must match a replacement block's string.

```dart
@GET(path: "/{id}")
Future<Response> getItemById(@Path() String id);
```

As an alternative, you can set the `@Path` annotation's `name` parameter to match a replacement block's string while using a different parameter name, like in the following example:

```dart
@GET(path: "/{id}")
Future<Response> getItemById(@Path("id") int itemId);
```

> Chopper uses String interpolation to replace replacement blocks with the provided values in the request URLs.

## Query parameters

Dynamic query parameters can be added to the URL by adding parameters to a request method annotated with the `@Query` annotation. Default values are supported.

```dart
Future<Response> search(
    @Query() String name, {
    @Query("count") int numberOfResults = 42,
});
```

If the parameter of the `@Query` annotation is not set, Chopper will use the actual name of the annotated parameter as the key for the query parameter in the URL.

If you prefer to pass a `Map` of query parameters, you can do so with the `@QueryMap` annotation.

```dart
Future<Response> search(@QueryMap() Map<String, dynamic> query);
```

Chopper encodes nested query maps and iterables with `qs_dart`. Repeated query parameters are the default list format:

```dart
@GET(path: "/search")
Future<Response> search(@Query("tag") List<String> tags);

await service.search(["dart", "http"]);
// /search?tag=dart&tag=http
```

Use the HTTP method annotation's `listFormat`, `dateFormat` and `includeNullQueryVars` options when an endpoint needs different query encoding:

```dart
@GET(
  path: "/events",
  listFormat: ListFormat.brackets,
  dateFormat: DateFormat.date,
  includeNullQueryVars: true,
)
Future<Response> events({
  @Query("days") List<int?> days = const [1, null, 3],
  @Query("from") required DateTime from,
});
```

If a query value needs custom serialization before the URL is encoded, configure a `ParameterConverter` on the `ChopperClient`, or use a client converter that also implements `ParameterConverter`. Parameter conversion currently applies to query values only; query keys are left unchanged.

## Request body

Use the `@Body` annotation on a request method parameter to specify data that will be sent as the request's body.

```dart
@POST(path: "todo/create")
Future<Response> postData(@Body() String data);
```

{% hint style="warning" %}
Chopper does not automatically convert `Object`s to `Map` and then `JSON`.

You have to pass a [Converter](/chopper/converters/converters.md) instance to a `ChopperClient` for JSON conversion to happen. See [built\_value\_converter](/chopper/converters/built-value-converter.md#built-value) for an example Converter implementation.
{% endhint %}

## Headers

Request headers can be set by providing a `Map<String, String>` object to the `headers` parameter each of the HTTP verb annotations have.

```dart
@GET(path: "/", headers: {"foo": "bar"})
Future<Response> fetch();
```

The `@Header` annotation can be used on method parameters to set headers dynamically for each request call.

```dart
@GET(path: "/")
Future<Response> fetch(@Header("foo") String bar);
```

> Setting request headers dynamically is also supported by [Interceptors](/chopper/interceptors.md) and [Converters](/chopper/converters/converters.md).
>
> As Chopper invokes Interceptors and Converter(s) *after* creating a Request, Interceptors and Converters *can* override headers set with the `headers` parameter or `@Header` annotations.

## Sending `application/x-www-form-urlencoded` data

If no converter is specified for a request (neither on the `ChopperClient` nor with `@FactoryConverter`) and the request body is a `Map<String, String>`, the body will be sent as form URL encoded data.

> This is the default behavior of the http package.

### FormUrlEncoded annotation

Use `@FormUrlEncoded()` or `@formUrlEncoded` on a method to add the correct `content-type` and convert a `Map` into `Map<String, String>` for requests.

```dart
@POST(path: "form")
@FormUrlEncoded()
Future<Response> postForm(@Body() Map<String, String> fields);
```

### FormUrlEncodedConverter

You can also use `FormUrlEncodedConverter` to add the correct `content-type` and convert a `Map` into `Map<String, String>` for requests.

```dart

final chopper = ChopperClient(
  converter: FormUrlEncodedConverter(),
);
```

To do only a single type of request with form encoding in a service, use the provided `FormUrlEncodedConverter.requestFactory` method with the `@FactoryConverter` annotation.

```dart
@POST(
  path: "form",
  headers: {contentTypeKey: formEncodedHeaders},
)
@FactoryConverter(request: FormUrlEncodedConverter.requestFactory)
Future<Response> postForm(@Body() Map<String, String> fields);
```

### Defining fields individually

To specify fields individually, use the `@Field` annotation on method parameters. If the field's name is not provided, the parameter's name is used as the field's name.

```dart
@POST(path: "form")
@FormUrlEncoded()
Future<Response> post(@Field() String foo, @Field("b") int bar);
```

## Sending files with `@Multipart`

### Sending a file in bytes as `List<int>` using `@PartFile`

```dart
@POST(path: 'file')
@Multipart()
Future<Response> postFile(@PartFile('file') List<int> bytes);
```

### Sending a file as `MultipartFile` using `@PartFile` with extra parameters via `@Part`

```dart
@POST(path: 'file')
@Multipart()
Future<Response> postMultipartFile(
  @PartFile() MultipartFile file,
  {
    @Part() String? id,
  },
);
```

### Sending multiple files as `List<MultipartFile>` using `@PartFile`

```dart
@POST(path: 'files')
@Multipart()
Future<Response> postListFiles(@PartFile() List<MultipartFile> files);
```

## Defining responses

ChopperService methods need to return a `Future`. It is possible to define return types of `Future<Response>` or `Future<Response<T>>` where `T` is the type of the response body. When `Response` is not needed for a request, it is also possible to define a return type of `Future<T>` where `T` is the type of the response body.

Chopper will generate a client which returns the specified return type. When the method does not directly return `Response`, Chopper returns `response.bodyOrThrow`; unsuccessful responses throw the response error when it is an `Exception`, otherwise a `ChopperHttpException`. A successful response whose converted body is `null` also throws a `ChopperHttpException`; use `Future<Response<T>>` and read `response.body` directly for endpoints where a successful `null` body is expected, such as HTTP 204/no-content responses.

```dart
// Returns a Response<dynamic>
@GET(path: "/")
Future<Response> fetch();

// Returns a Response<MyClass>
@GET(path: "/")
Future<Response<MyClass>> fetch();

// Returns a MyClass
@GET(path: "/")
Future<MyClass> fetch();
```

> Note: Chopper doesn't convert response bodies by itself to Dart objects. You need to use a [Converter](/chopper/converters/converters.md) for that.

## Add tag

Use the `@Tag()` parameter annotation to set `request.tag` on the underlying Chopper `Request`. Tags can be read in `Converter`s or `Interceptor`s for tracing, analytics, varying behavior, and more.

For example, a converter can use a tag to decide whether null or empty body values should be included for one request.

```dart
class IncludeBodyNullOrEmptyTag {
  const IncludeBodyNullOrEmptyTag({
    this.includeNull = false,
    this.includeEmpty = false,
  });

  final bool includeNull;
  final bool includeEmpty;
}

@GET(path: '/include')
Future<Response> includeBodyNullOrEmptyTag({
  @Tag() IncludeBodyNullOrEmptyTag tag = const IncludeBodyNullOrEmptyTag(),
});
```

Read the tag via `request.tag` in a `Converter` or `Interceptor`:

```dart
class TagConverter extends JsonConverter {
  @override
  Request convertRequest(Request request) {
    final tag = request.tag;
    if (tag is IncludeBodyNullOrEmptyTag) {
      if (request.body is Map) {
        final Map body = request.body as Map;
        final Map bodyCopy = {};
        for (final MapEntry entry in body.entries) {
          if (!tag.includeNull && entry.value == null) continue;
          if (!tag.includeEmpty && entry.value == "") continue;
          bodyCopy[entry.key] = entry.value;
        }
        request = request.copyWith(body: bodyCopy);
      }
    }
    return super.convertRequest(request);
  }
}
```
