# Handling Commands over HTTP

To accept a command over HTTP, define a request type with JSON annotations for the body, and a function that returns the command. It receives the request, the body, decoded into the request type, and the user, as the function that returns a query does (see **[Handling Queries over HTTP](/docs/architecturekit/v2.0/handling-queries-over-http)**). So it takes from the request what the body does not hold, such as a value of the path, a header, or the context.

For example, for the `BorrowBook` that checks the revision of the caller (see **[Checking the Revision of the Caller](/docs/architecturekit/v2.0/using-preconditions#checking-the-revision-of-the-caller)**), with the ID of the book in the path:

```go
type borrowBookRequest struct {
  BorrowedUntil   string `json:"borrowedUntil"`
  ExpectedEventID string `json:"expectedEventId"`
}

func toBorrowBook(r *http.Request, request borrowBookRequest, user User) (BorrowBook, error) {
  bookID := r.PathValue("id")
  if err := bookSubject.Check(bookID); err != nil {
    return BorrowBook{}, err
  }
  if _, err := time.Parse(time.DateOnly, request.BorrowedUntil); err != nil {
    return BorrowBook{}, errors.New("borrowedUntil must be a date")
  }
  if _, err := architecturekit.ParseRevision(request.ExpectedEventID); err != nil {
    return BorrowBook{}, errors.New("expectedEventId must be an event ID")
  }

  return BorrowBook{
    BookID:          bookID,
    ReaderID:        user.ID,
    BorrowedUntil:   request.BorrowedUntil,
    ExpectedEventID: request.ExpectedEventID,
  }, nil
}
```

The function is the place to validate a request, since an error it returns is answered with `400 Bad Request`, unless it has a status code of its own (see **[Authorizing Commands](/docs/architecturekit/v2.0/handling-commands-over-http#authorizing-commands)**). Check at least what would otherwise fail later: the ID of the book becomes part of a subject, and `Build` panics on an empty ID or one with a character that a segment of a subject may not contain, such as a slash or a dot (see **[Composing Subjects](/docs/architecturekit/v2.0/composing-subjects)**), which is answered with `500 Internal Server Error`. A value of the path is no exception, since it may hold a slash, sent as `%2F`. And a value that does not match the schema of its event is refused by the database, which is a permanent failure answered with `500 Internal Server Error` – although it is the caller's mistake. An expected event ID that is empty or not an event ID at all is answered with `400 Bad Request` without a check of your own, since `OnEventID` refuses it before anything is read (see **[Checking the Revision of the Caller](/docs/architecturekit/v2.0/using-preconditions#checking-the-revision-of-the-caller)**). Checking it here lets the message name the field, though. `ParseRevision` refuses both, unlike `CompareRevisions`, which takes an empty revision for the one of a view that has seen nothing (see **[Comparing Revisions](/docs/architecturekit/v2.0/reading-your-own-writes#comparing-revisions)**).

Then call the `Route` function with the API, the mux, a pattern, the function that returns the command, and the decider:

```go
httpapi.Route(api, mux, "POST /api/books/{id}/borrow", toBorrowBook, borrowBook)
```

A command changes something, so the pattern names a method that may do so, usually `POST`. A pattern without a method, such as `/api/books/{id}/borrow`, accepts every method, `GET` included, and `GET`, `HEAD`, `OPTIONS`, and `QUERY` must not change anything. A browser sends `GET` and `HEAD` from another site without asking, for example for a link that the user follows, along with the cookies of the user, and a route that takes no body lets `GET`, `HEAD`, and `OPTIONS` pass from another origin (see **[Handling Commands Without a Body](/docs/architecturekit/v2.0/handling-commands-over-http#handling-commands-without-a-body)**). `QUERY` may change nothing either, since HTTP defines it as safe and repeatable, so a client or a cache may send it again. So `Route` panics for a pattern without a method, or with one of these, rather than executing the command for any site that links to it, or whenever a query is sent again.

The route decodes the request body, builds the command, and executes it:

```shell
curl -X POST http://localhost:8080/api/books/42/borrow \
  -H "Content-Type: application/json" \
  -d '{"borrowedUntil":"2026-10-24","expectedEventId":"0"}'
```

If this succeeds, it answers with `200 OK` and the revision it has written, which is the ID of the last written event:

```json
{ "revision": "1" }
```

The revision is empty if the command did not write anything. A caller hands it to a query to read its own writes (see **[Reading Your Own Writes over HTTP](/docs/architecturekit/v2.0/reading-your-own-writes-over-http)**).

Otherwise, it answers with the status code that matches the error (see **[Mapping Errors to Status Codes](/docs/architecturekit/v2.0/mapping-errors-to-status-codes)**) and a message:

```json
{ "message": "book 42 is already borrowed" }
```

The message is the error message if the error is written for the caller, such as a broken business rule or what is wrong with the request. Other errors may name internals, such as the key a token failed to verify with, or the subject a precondition guarded, so their message is fixed:

| Status code | Message |
|---|---|
| `401 Unauthorized` | `unauthorized` |
| `404 Not Found`, for a query that found no item | `not found` |
| `409 Conflict` | `conflict: the data has changed since it was read` |
| `499 Client Closed Request` | `request canceled` |
| `500 Internal Server Error`, for a write of the command whose outcome is unknown | `outcome unknown: the request may have succeeded` |
| `500 Internal Server Error`, for a view that did not catch up with a write after the command | `the request succeeded, but its result is not visible yet` |
| `500` and above | `internal server error` |

The actual error is logged, so that it does not vanish (see **[Setting Up an HTTP API](/docs/architecturekit/v2.0/setting-up-an-http-api)**): at level `Info` for `401`, `404`, and `409`, since the server did not fail, and at level `Error` for `500` and above. For `404`, that holds only for a query that found no item (see **[Reporting Missing Items](/docs/architecturekit/v2.0/handling-queries-over-http#reporting-missing-items)**). A request that was canceled, usually because the caller went away, is not logged, since nothing failed.

If handling a request panics, for example because `Build` received an ID that was not checked, the route answers with `500 Internal Server Error` and the message `internal server error`, like any other internal failure, and logs the panic at level `Error`, with its value and its stack. Otherwise, `net/http` would close the connection, and the caller would get no answer at all.

*Note that a panic with `http.ErrAbortHandler` is passed on, since `net/http` expects it to abort the response.*

To answer this way in a handler of your own, call the `Respond` function with the response writer, the request, the API, the written events, and the error. Like the route, it logs through the logger of the API, with the route of the request. Unlike the route, it does not know where an error comes from, so an error without a status code of its own is answered with `500 Internal Server Error`, also if the handler has found a mistake in the request itself. Wrap such an error with `httpapi.ErrMalformed`, for example with `fmt.Errorf("%w: %w", httpapi.ErrMalformed, err)`, to answer it with `400 Bad Request`.

*Note that the function has the type `httpapi.ToCommand`. The request type only describes the body, so it may come from another package, for example one that the application shares with its clients.*

*Note that calling `Route` with `nil` as the API, as the function, or as one of the options, or with a `Decider` that was not created with `NewDecider`, panics, rather than failing every request.*

## Handling Commands Without a Body

Some commands take everything they need from the path, such as `ReturnBook`, which only needs the ID of the book. Such a command takes no body, so its function has the request type `httpapi.NoBody`:

```go
func toReturnBook(r *http.Request, _ httpapi.NoBody, user User) (ReturnBook, error) {
  bookID := r.PathValue("id")
  if err := bookSubject.Check(bookID); err != nil {
    return ReturnBook{}, err
  }

  return ReturnBook{BookID: bookID}, nil
}

httpapi.Route(api, mux, "POST /api/books/{id}/return", toReturnBook, returnBook)
```

The route then expects no body, so the caller sends neither a `Content-Type` header nor a body:

```shell
curl -X POST http://localhost:8080/api/books/42/return
```

A body of `{}` is accepted as well, whatever the `Content-Type` header says, so that a caller that sends one out of habit keeps working. Any other body is answered with `400 Bad Request`, and the message says that the route takes no body.

Requiring `application/json` is what keeps a browser from sending a command from another site without asking the server first, since a form can not send JSON. A route without a body can not rely on that, so before it looks at the body, it checks where the request comes from, with the `CrossOriginProtection` of `net/http`. A request that a browser sends from another origin, which the `Sec-Fetch-Site` header says, or, without it, an `Origin` header whose host differs from the `Host` header, is answered with `403 Forbidden`, and the error is `httpapi.ErrForbidden`. A request from the same origin passes, and so does one without these headers, such as one of `curl` or of another server.

*Note that this also refuses a browser frontend that runs on another origin than the API, even on another subdomain, so such a frontend can not call a route without a body for now.*

The check lets a request with `GET`, `HEAD`, or `OPTIONS` pass from any origin, since these methods must not change anything. That is why `Route` refuses a pattern with one of them, or without a method, which accepts every method (see above). Wire a handler of your own that executes a command to a method that may change something as well, such as `POST`.

## Adding to the Answer

To answer with more than the revision, for example with the ID that `toAcquireBook` below makes up for a new book, hand over the `Adding` option. It takes a function that receives a `Handled` value with the command and the written events, and returns the fields to add, usually as a struct with JSON annotations, and an error:

```go
type acquireBookRequest struct {
  Title  string `json:"title"`
  Author string `json:"author"`
  ISBN   string `json:"isbn"`
}

func toAcquireBook(r *http.Request, request acquireBookRequest, user User) (AcquireBook, error) {
  return AcquireBook{
    BookID: rand.Text(),
    Title:  request.Title,
    Author: request.Author,
    ISBN:   request.ISBN,
  }, nil
}

httpapi.Route(api, mux, "POST /api/acquire-book", toAcquireBook, acquireBook,
  httpapi.Adding(func(handled httpapi.Handled[AcquireBook]) (any, error) {
    return struct {
      ID string `json:"id"`
    }{handled.Command.BookID}, nil
  }))
```

The route then answers with both:

```json
{ "id": "…", "revision": "1" }
```

The function is only called if the command has succeeded. A value that encodes to `null` adds no fields. That is `nil`, and also a `nil` pointer of a concrete type, which an interface does not count as `nil`, such as the one that `return findShelf(handled)` hands back if `findShelf` returns a `*Shelf` and an error, and fails. The fields are encoded as the result of a query is, so a `nil` slice or map in them is answered as `[]` or `{}` (see **[Handling Queries over HTTP](/docs/architecturekit/v2.0/handling-queries-over-http)**).

If the function returns an error, the events are written all the same, so the route still answers with `200 OK` and the revision, which the caller needs to read its own writes, and must not take for a reason to send the command again, whatever is wrong with the fields the function returned along with the error. The answer then holds these fields if they can be used, and none otherwise. The error is logged through the logger of the API, with the route, and so is why the fields were dropped, if they were.

The kit adds the revision itself, so the fields must not contain one, and they must encode to a JSON object, so they must not hold `NaN`, for example, which JSON has no number for. Otherwise, unless the function has returned an error, the route answers with `500 Internal Server Error` and logs why, although the events have been written, since that is a mistake in the code rather than something that happens at runtime.

*Note that the written events are available in `Handled` as well. Add them only deliberately: they are the inner model of the application, every caller that reads them depends on their shape, and they may contain data that is not meant for the caller.*

*Note that calling `Adding` with `nil`, or giving it twice, panics.*

## Answering Commands in Your Own Format

To answer in a format of your own, for example with another status code, call the `Handle` function in a handler of your own. It does the same as a route, but writes nothing to the response. Instead, it returns a `Handled` value with the command it has built and the written events:

```go
mux.HandleFunc("POST /api/acquire-book", func(w http.ResponseWriter, r *http.Request) {
  handled, err := httpapi.Handle(r, api, toAcquireBook, acquireBook)
  if err != nil {
    httpapi.Respond(w, r, api, nil, err)
    return
  }

  w.Header().Set("Content-Type", "application/json")
  w.WriteHeader(http.StatusCreated)
  json.NewEncoder(w).Encode(map[string]string{
    "id": handled.Command.BookID,
  })
})
```

*Note that `Handled` contains the command even if executing it fails.*

*Note that `Handle` returns a panic as an error, which `StatusFor` maps to `500`, and which `Respond` logs with the value and the stack of the panic.*

*Note that calling `Handle` with `nil` as the API or as the function, or with a `Decider` that was not created with `NewDecider`, panics, as with `Route`, but on every request, even one whose caller is unknown. That panic, too, comes back as an error, which names the mistake.*

## Authorizing Commands

To refuse a command, return `httpapi.ErrForbidden` from the function that returns it. The request is then answered with `403 Forbidden`, and the command is not executed:

```go
func toAcquireBook(r *http.Request, request acquireBookRequest, user User) (AcquireBook, error) {
  if !user.IsLibrarian {
    return AcquireBook{}, httpapi.ErrForbidden
  }

  return AcquireBook{
    BookID: rand.Text(),
    Title:  request.Title,
    Author: request.Author,
    ISBN:   request.ISBN,
  }, nil
}
```

The same applies to every error that has a status code of its own (see **[Mapping Errors to Status Codes](/docs/architecturekit/v2.0/mapping-errors-to-status-codes)**), such as `httpapi.ErrNotFound` or an error of the category `ErrDomain`, and to an error of the category `ErrPermanent`, one of `ErrOutcomeUnknown`, or one of `ErrNotCaughtUp`, which are answered with `500 Internal Server Error` and the message `internal server error`, since the command has not run. So if the function looks something up in another service, with the context of the request, and that service is down, it returns an error of the category `ErrTransient`. The request is then answered with `503 Service Unavailable`, and the failure is logged, rather than blaming the request.

Any other error returned from the function is answered with `400 Bad Request`, with the error as the message, after the text of `httpapi.ErrMalformed`, as in `malformed request: borrowedUntil must be a date`. In `Handle`, it wraps `httpapi.ErrMalformed` as well as the original error, so that `errors.Is` and `errors.As` find either.

## Validating Requests

Before the function that returns the command receives the body, the request is validated, as it is for a query (see **[Handling Queries over HTTP](/docs/architecturekit/v2.0/handling-queries-over-http)**):

- The `Content-Type` header must be `application/json`, otherwise the request is answered with `415 Unsupported Media Type`, and the error is `httpapi.ErrUnsupportedMediaType`.
- The body must not be larger than `httpapi.MaxRequestBody`, which is one mebibyte, otherwise the request is answered with `413 Request Entity Too Large`, and the error is `httpapi.ErrTooLarge`. The same goes for a body that a middleware cuts off at a lower limit with `http.MaxBytesReader`.
- The body must be a single valid JSON value without unknown fields, otherwise the request is answered with `400 Bad Request`, and the error is `httpapi.ErrMalformed`, which wraps the error of decoding, so that `errors.As` finds it. Nothing but whitespace may follow the value, and no name may occur twice in an object. Names match fields regardless of case, as with `encoding/json`, so two names that match the same field count as the same name, even if they differ in case. The body must not be `null`, which `encoding/json` would turn into the zero value of the request type, unless the request type decodes itself, and so decides itself what `null` means, or is an interface, which takes `null` for `nil`, while `null` for a field still leaves the field as it is. And an array must have as many elements as the Go array it is decoded into, such as a `[2]int`, which `encoding/json` would otherwise fill up with zeros, or cut off.

*Note that parsers disagree on what a name that occurs twice means, and on data after the value: one takes the first value, another the last, and one stops after the value, while another reads on. A filter or a proxy in front of the application might then check another value than the one the application uses, which is why both are refused.*

*(Changed in 2.0)* If the body can not be decoded, the message says what is wrong in words of its own, rather than in those of the decoder, which name the types of Go. After `malformed request: `, it says, for example, `empty body`, `invalid JSON`, `unknown field "borowedUntil"`, `duplicate field "expectedEventId"`, `"borrowedUntil" must be a string`, `the body must be an object`, for a body of `null` as well, or `"pair" must have 2 elements`. A nested value is named by its path, such as `"items.0.bookId"`. For a failure that it has no words of its own for, such as a value of a type that JSON has no kind for, it says that the value can not be decoded, as in `"rating" can not be decoded`, or `the body can not be decoded`. A body that can not even be read, for example because a middleware fails to decompress it, gives `the body could not be read`, without the words of the middleware. The error of a type that decodes itself, on the other hand, keeps its own text, as it is, also for a type of a library, such as `netip.Addr`, as long as it can tell its text. If its `Error` function panics, as that of a `*json.UnmarshalTypeError` without a type does, the message says that the value can not be decoded instead. For the full list, see the **[documentation of `BodyOf`](https://pkg.go.dev/github.com/thenativeweb/architecturekit-golang/v2/architecturekit/httpapi#BodyOf)**.

If the request type is `httpapi.NoBody`, the request is validated differently (see **[Handling Commands Without a Body](/docs/architecturekit/v2.0/handling-commands-over-http#handling-commands-without-a-body)**): a request that a browser sends from another origin is answered with `403 Forbidden` first, and the error is `httpapi.ErrForbidden`. The `Content-Type` header is not required, and the body must be empty or `{}`, otherwise the request is answered with `400 Bad Request`, and the error is `httpapi.ErrMalformed`. A body larger than `httpapi.MaxRequestBody` is still answered with `413 Request Entity Too Large`.

To read a body by the same rules in a handler of your own, call the `BodyOf` function with the request type as its type parameter, as in `httpapi.BodyOf[borrowBookRequest](r)`. With `httpapi.NoBody`, it checks that a request has no body, as the route does.
