diff --git a/README.md b/README.md
index f5d0a25..9fa53f8 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,91 @@
# axon
+**WIP**
+
+HTTP server library inspired by [`axum`](https://docs.rs/latest/axum), allowing best-in-class
+expressive routing.
+
+```purs
+root :: Get -> Path "/" _ -> Aff String
+root _ _ = pure "Hello, world!"
+
+main :: Effect Unit
+main = Axon.serve (root `Handle.or` Handle.Default.notFound)
+
+-- GET localhost:8000/ -> 200 OK "Hello, world!"
+-- GET localhost:8000/foo -> 404 Not Found
+```
+
+## Request Handlers
+Request handler functions have any number of parameters that are `RequestParts` and return an `Aff Response` (or any `MonadAff`).
+
+
+
+
+`RequestParts`
+
+
+ - `Request`
+ - Always succeeds; provides the entire request
+ - **Combinators**
+ - `Unit`
+ - Always succeeds
+ - `a /\ b`
+ - Tuple of `a` and `b`, where `a` and `b` are `RequestParts`.
+ - `Maybe a`
+ - `a` must be `RequestParts`. If `a` can't be extracted, the handler will still succeed and this will be `Nothing`. If `a` was extracted, it's wrapped in `Just`.
+ - `Either a b`
+ - `a` and `b` must be `RequestParts`. Succeeds if either `a` or `b` succeeds (preferring `a`). Fails if both fail.
+ - **Body**
+ - `String`
+ - succeeds when request has a non-empty body that is valid UTF-8
+ - `Json a`
+ - succeeds when request has a `String` body (see above) that can be parsed into `a` using `DecodeJson`.
+ - `Buffer`
+ - succeeds when request has a nonempty body.
+ - `Stream`
+ - succeeds when request has a nonempty body.
+ - **Headers**
+ - `Header a`
+ - `a` must be `TypedHeader` from `Axon.Header.Typed`. Allows statically (ex. `ContentType Type.MIME.Json`) or dynamically (ex. `ContentType String`) matching request headers.
+ - `HeaderMap`
+ - All headers provided in the request
+ - **Path**
+ - `Path a c`
+ - Statically match the path of the request, and extract parameters. See `Axon.Request.Parts.Path`. (TODO: this feels too magical, maybe follow axum's prior art of baking paths into the router declaration?)
+ - **Method**
+ - `Get`
+ - `Post`
+ - `Put`
+ - `Patch`
+ - `Delete`
+ - `Options`
+ - `Connect`
+ - `Trace`
+
+
+Similarly to the structural extraction of request parts; handlers can use `Axon.Response.Construct.ToResponse` for easily constructing responses.
+
+
+
+
+`ToResponse`
+
+
+- **Combinators**
+ - `Status /\ a`
+ - Special case to make sure any `Status` in a tuple will take priority over any default statuses within. TODO: This case (overlapping with `a /\ b` requires the class to be "sealed" in an instance chain. Want a clean way around this so consumers can implement `ToResponse`.)
+ - `a /\ b`
+ - Merges `toResponse a` and `toResponse b`, using `b` on conflicts
+- **Status**
+ - `Axon.Response.Status.Status`
+- **Body**
+ - `Axon.Response.Body.Body`
+ - `String`
+ - `Node.Buffer.Buffer`
+ - `Node.Stream.Readable a` (for all `a`)
+ - `Axon.Response.Construct.Json a`
+ - `a` must be `EncodeJson`. This will set the body to `a` stringified, and set `Content-Type` to `application/json`.
+- **Headers**
+ - `ToResponse` is implemented for all implementors of `TypedHeader`
+ - TODO: `Map String String`
+