diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,30 @@
+Copyright Anton Kholomiov (c) 2023
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+
+    * Neither the name of Author name here nor the names of other
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,4 @@
+# mig-client
+
+Library to build HTTP-clients.
+See tutorial on how to build clients: [Client article](https://anton-k.github.io/mig/08-client.html).
diff --git a/mig-client.cabal b/mig-client.cabal
new file mode 100644
--- /dev/null
+++ b/mig-client.cabal
@@ -0,0 +1,56 @@
+cabal-version: 1.12
+
+-- This file has been generated from package.yaml by hpack version 0.35.2.
+--
+-- see: https://github.com/sol/hpack
+
+name:               mig-client
+version:            0.1.0.0
+synopsis:           Build http-clients from API definition for mig servers
+description:        With this library we can build client functions for HTTP-applications
+                    from the same code as server definition.
+category:           Web
+homepage:           https://github.com/anton-k/mig#readme
+bug-reports:        https://github.com/anton-k/mig/issues
+author:             Anton Kholomiov
+maintainer:         anton.kholomiov@gmail.com
+copyright:          2023 Anton Kholomiov
+license:            BSD3
+license-file:       LICENSE
+build-type:         Simple
+extra-source-files:
+    README.md
+
+source-repository head
+  type: git
+  location: https://github.com/anton-k/mig
+
+library
+  exposed-modules:
+      Mig.Client
+  other-modules:
+      Paths_mig_client
+  hs-source-dirs:
+      src
+  default-extensions:
+      OverloadedStrings
+      TypeFamilies
+      OverloadedRecordDot
+      DuplicateRecordFields
+      LambdaCase
+      DerivingStrategies
+      StrictData
+      AllowAmbiguousTypes
+  ghc-options: -Wall -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wmissing-export-lists -Wmissing-home-modules -Wpartial-fields -Wredundant-constraints -Wunused-packages
+  build-depends:
+      base >=4.7 && <5
+    , bytestring
+    , containers
+    , http-api-data
+    , http-client
+    , http-media
+    , http-types
+    , mig >=0.2
+    , mtl
+    , text
+  default-language: GHC2021
diff --git a/src/Mig/Client.hs b/src/Mig/Client.hs
new file mode 100644
--- /dev/null
+++ b/src/Mig/Client.hs
@@ -0,0 +1,450 @@
+{-# LANGUAGE AllowAmbiguousTypes #-}
+{-# LANGUAGE UndecidableInstances #-}
+
+-- | Functions to create http-clients from the same code as server or API schema
+module Mig.Client (
+  ToClient (..),
+  Client (..),
+  ClientConfig (..),
+  runClient,
+  (:|) (..),
+  FromClient (..),
+  getRespOrValue,
+  Client' (..),
+  runClient',
+  MonadIO (..),
+  ClientOr,
+) where
+
+import Control.Monad
+import Control.Monad.IO.Class
+import Control.Monad.Reader
+import Data.Bifunctor
+import Data.ByteString (ByteString)
+import Data.ByteString qualified as B
+import Data.ByteString.Lazy qualified as BL
+import Data.Kind
+import Data.Map.Strict qualified as Map
+import Data.Proxy
+import Data.String
+import Data.Text qualified as Text
+import Data.Text.Encoding qualified as Text
+import GHC.TypeLits
+import Network.HTTP.Client qualified as Http
+import Network.HTTP.Media.RenderHeader (RenderHeader (..))
+import Network.HTTP.Types.Header (HeaderName)
+import Network.HTTP.Types.Method
+import Network.HTTP.Types.Status
+import Web.HttpApiData
+
+import Mig.Core
+
+{-| Infox synonym for pair. It can be useful to stack together
+many client functions in the output of @toClient@ function.
+-}
+data (:|) a b = a :| b
+
+instance (ToClient a, ToClient b) => ToClient (a :| b) where
+  toClient api = a :| b
+    where
+      (a, b) = toClient api
+
+  clientArity = clientArity @(a, b)
+
+class MapRequest a where
+  mapRequest :: (Http.Request -> Http.Request) -> (a -> a)
+  mapCapture :: (CaptureMap -> CaptureMap) -> (a -> a)
+
+instance MapRequest (Client a) where
+  mapRequest f (Client a) = Client (\conf capt -> a conf capt . f)
+  mapCapture f (Client a) = Client (\conf capt req -> a conf (f capt) req)
+
+instance MapRequest (Send method Client a) where
+  mapRequest f (Send client) = Send (mapRequest f client)
+  mapCapture f (Send client) = Send (mapCapture f client)
+
+instance (MapRequest b) => MapRequest (a -> b) where
+  mapRequest f a = mapRequest f . a
+  mapCapture f a = mapCapture f . a
+
+instance (MapRequest a, MapRequest b) => MapRequest (a, b) where
+  mapRequest f (a, b) = (mapRequest f a, mapRequest f b)
+  mapCapture f (a, b) = (mapCapture f a, mapCapture f b)
+
+instance (MapRequest a, MapRequest b) => MapRequest (a :| b) where
+  mapRequest f (a :| b) = (mapRequest f a :| mapRequest f b)
+  mapCapture f (a :| b) = (mapCapture f a :| mapCapture f b)
+
+instance (MapRequest a, MapRequest b, MapRequest c) => MapRequest (a, b, c) where
+  mapRequest f (a, b, c) = (mapRequest f a, mapRequest f b, mapRequest f c)
+  mapCapture f (a, b, c) = (mapCapture f a, mapCapture f b, mapCapture f c)
+
+instance (MapRequest a, MapRequest b, MapRequest c, MapRequest d) => MapRequest (a, b, c, d) where
+  mapRequest f (a, b, c, d) = (mapRequest f a, mapRequest f b, mapRequest f c, mapRequest f d)
+  mapCapture f (a, b, c, d) = (mapCapture f a, mapCapture f b, mapCapture f c, mapCapture f d)
+
+{-| Creates http-client from server definition.
+
+The result adapts to decalred types. It creates so many client functions as
+the arity of the tuple in the result. The server can have more handlers than in the result
+Routes from result definition and server definition are matched in the same order as they are declared.
+
+The type of the client is derived from the type signature of the result. The information
+on paths for handlers is derived from server definition.
+
+To use the same code for both client and server it is convenient to declare
+signatures for handlers as type synonyms parameterized by server monad.
+And in the server implementation monad is going to be something IO-based but
+in client handlers it will be `Client`-monad.
+
+For example for a server:
+
+> type Hello m = Capture "name" Text -> Get m (Resp Text)
+> type Add m = Query "a" Int -> Query "b" Int -> Get m (Resp Int)
+>
+> server :: Server IO
+> server = "api" /.
+>   mconcat
+>    [ "hello" /. helloHandler
+>    , "add" /. addHandler
+>    ]
+>
+> helloHandler :: Hello IO
+> helloHandler (Capture name) = Send $ pure $ ok $ "Hello " <> name
+>
+> addHandler :: Add IO
+> addHandler (Query a) (Query b) = Send $ pure $ ok (a + b)
+
+We can define the client and reuse type signatures that we have defined in the server code:
+
+> helloClient :: Hello Client
+> addClient :: Add Client
+>
+> helloClient :| addClient = toClient server
+
+If there is no definition for server. For example if we write implementation for
+some external server or API provided by third party we can use recursive definition in the server.
+For example if there is no haskell implementation for the server in the previous example. But we
+know the API of the application we can define client with recursive definition:
+
+> type Hello m = Capture "name" Text -> Get m (Resp Text)
+> type Add m = Query "a" Int -> Query "b" Int -> Get m (Resp Int)
+>
+> helloClient :: Hello Client
+> addClient :: Add Client
+>
+> helloClient :| addClient = toClient server
+>
+> server :: Server Client
+> server = "api" /.
+>   mconcat
+>    [ "hello" /. helloClient
+>    , "add" /. addClient
+>    ]
+
+The code does not get stuck into recursion loop because implementation of the route handlers
+is not needed to create client functions. The function @toClient@ takes into account
+only type-signatures of the handlers and paths.
+-}
+class (MapRequest a) => ToClient a where
+  -- | converts to client function
+  toClient :: Server m -> a
+
+  -- | how many routes client has
+  clientArity :: Int
+
+-- | Config to run the clients
+data ClientConfig = ClientConfig
+  { port :: Int
+  -- ^ port to connect to
+  , manager :: Http.Manager
+  -- ^ HTTP-manager
+  }
+
+-- | The client monad. All errors are unified to Lazy.ByteString.
+newtype Client a = Client (ClientConfig -> CaptureMap -> Http.Request -> IO (RespOr AnyMedia BL.ByteString a))
+  deriving (Functor)
+
+instance Applicative Client where
+  pure a = Client $ const $ const $ const $ pure $ pureResp a
+  (<*>) = ap
+
+pureResp :: a -> RespOr AnyMedia BL.ByteString a
+pureResp a = RespOr $ Right $ Resp ok200 [] (Just a)
+
+instance Monad Client where
+  (Client ma) >>= mf = Client $ \config captureValues req -> do
+    RespOr eResp <- ma config captureValues req
+    case eResp of
+      Right resp -> case resp.body of
+        Just body -> case mf body of
+          Client run -> run config captureValues req
+        Nothing -> pure (RespOr $ Right $ Resp resp.status resp.headers Nothing)
+      Left err -> pure (RespOr $ Left err)
+
+instance MonadIO Client where
+  liftIO act = Client $ \_ _ _ -> pureResp <$> act
+
+-- | Runs client. It calls client handler and fetches the result.
+runClient :: ClientConfig -> Client a -> IO (RespOr AnyMedia BL.ByteString a)
+runClient config (Client act) = act config mempty Http.defaultRequest
+
+instance (IsMethod method, FromReqBody (RespMedia a) (RespBody a), IsResp a) => ToClient (Send method Client a) where
+  toClient api =
+    mapRequest (setRequestMethod (toMethod @method)) $
+      Send $
+        fmap ok $
+          httpSend @(RespMedia a) @(RespBody a) (getHeadPath $ Server $ fillCaptures $ unServer api)
+
+  clientArity = 1
+
+instance (ToClient a, ToClient b) => ToClient (a, b) where
+  toClient (Server api) = (toClient (Server apiA), toClient (Server apiB))
+    where
+      (apiA, apiB) = bimap fromFlatApi fromFlatApi $ Prelude.splitAt (clientArity @a) (flatApi api)
+
+  clientArity = clientArity @a + clientArity @b
+
+instance (ToClient a, ToClient b, ToClient c) => ToClient (a, b, c) where
+  toClient api = (a, b, c)
+    where
+      (a, (b, c)) = toClient api
+
+  clientArity = clientArity @(a, (b, c))
+
+instance (ToClient a, ToClient b, ToClient c, ToClient d) => ToClient (a, b, c, d) where
+  toClient api = (a, b, c, d)
+    where
+      (a, (b, c, d)) = toClient api
+
+  clientArity = clientArity @(a, (b, c, d))
+
+getHeadPath :: Server m -> Path
+getHeadPath (Server api) = case flatApi api of
+  (pathHead, _) : _ -> pathHead
+  _ -> error "Not enought methods. API is empty"
+
+setRequestMethod :: Method -> Http.Request -> Http.Request
+setRequestMethod m req = req{Http.method = m}
+
+instance (KnownSymbol sym, ToHttpApiData a, ToClient b) => ToClient (Header sym a -> b) where
+  toClient api = \header -> mapRequest (addHeader header) $ toClient @b api
+  clientArity = clientArity @b
+
+instance (KnownSymbol sym, ToHttpApiData a, ToClient b) => ToClient (OptionalHeader sym a -> b) where
+  toClient api = \header -> mapRequest (addOptionalHeader header) $ toClient @b api
+  clientArity = clientArity @b
+
+instance (KnownSymbol sym, ToHttpApiData a, ToClient b) => ToClient (Query sym a -> b) where
+  toClient api = \query -> mapRequest (addQuery query) $ toClient @b api
+  clientArity = clientArity @b
+
+instance (KnownSymbol sym, ToClient b) => ToClient (QueryFlag sym -> b) where
+  toClient api = \(QueryFlag flag) -> mapRequest (addQuery @sym @Bool (Query flag)) $ toClient @b api
+  clientArity = clientArity @b
+
+instance (KnownSymbol sym, ToHttpApiData a, ToClient b) => ToClient (Optional sym a -> b) where
+  toClient api = \query -> mapRequest (addOptional query) $ toClient @b api
+  clientArity = clientArity @b
+
+instance (KnownSymbol sym, ToHttpApiData a, ToClient b) => ToClient (Capture sym a -> b) where
+  toClient api = \capture -> mapCapture (addCapture capture) $ toClient @b api
+  clientArity = clientArity @b
+
+instance (ToRespBody media a, ToClient b) => ToClient (Body media a -> b) where
+  toClient api = \(Body body) -> mapRequest (addBody @media body) $ toClient @b api
+  clientArity = clientArity @b
+
+instance (ToClient b) => ToClient (PathInfo -> b) where
+  toClient api = \(PathInfo _path) -> toClient @b api
+  clientArity = clientArity @b
+
+instance (ToClient b) => ToClient (IsSecure -> b) where
+  toClient api = \(IsSecure _val) -> toClient @b api
+  clientArity = clientArity @b
+
+instance (ToClient b) => ToClient (RawRequest -> b) where
+  toClient api = \(RawRequest _val) -> toClient @b api
+  clientArity = clientArity @b
+
+instance (ToClient b) => ToClient (RawResponse -> b) where
+  toClient api = \(RawResponse _val) -> toClient @b api
+  clientArity = clientArity @b
+
+addQuery :: forall sym a. (KnownSymbol sym, ToHttpApiData a) => Query sym a -> Http.Request -> Http.Request
+addQuery (Query a) req = req{Http.queryString = str}
+  where
+    str =
+      if B.null (Http.queryString req)
+        then param
+        else Http.queryString req <> "&" <> param
+
+    param = fromString (symbolVal (Proxy @sym)) <> "=" <> Text.encodeUtf8 (toUrlPiece a)
+
+addOptional :: forall sym a. (KnownSymbol sym, ToHttpApiData a) => Optional sym a -> Http.Request -> Http.Request
+addOptional (Optional mVal) = case mVal of
+  Nothing -> id
+  Just val -> addQuery @sym (Query val)
+
+addHeader :: forall sym a. (KnownSymbol sym, ToHttpApiData a) => Header sym a -> Http.Request -> Http.Request
+addHeader (Header a) = addRequestHeader (fromString $ symbolVal (Proxy @sym), toHeader a)
+
+addOptionalHeader :: forall sym a. (KnownSymbol sym, ToHttpApiData a) => OptionalHeader sym a -> Http.Request -> Http.Request
+addOptionalHeader (OptionalHeader ma) = case ma of
+  Nothing -> id
+  Just a -> addHeader @sym (Header a)
+
+addCapture :: forall sym a. (KnownSymbol sym, ToHttpApiData a) => Capture sym a -> CaptureMap -> CaptureMap
+addCapture (Capture a) =
+  Map.insert (fromString (symbolVal (Proxy @sym))) (toUrlPiece a)
+
+addBody :: forall media a. (ToRespBody media a) => a -> Http.Request -> Http.Request
+addBody body req =
+  addRequestHeader ("Content-Type", renderHeader (toMediaType @media)) $
+    req{Http.requestBody = Http.RequestBodyLBS (toRespBody @media body)}
+
+addRequestHeader :: (HeaderName, ByteString) -> Http.Request -> Http.Request
+addRequestHeader header req = req{Http.requestHeaders = header : Http.requestHeaders req}
+
+pathToString :: CaptureMap -> Path -> ByteString
+pathToString captureValues (Path path) = case path of
+  [] -> mempty
+  item : rest -> flip rec (Path rest) $ case item of
+    StaticPath p -> p
+    CapturePath p ->
+      case Map.lookup p captureValues of
+        Just val -> val
+        Nothing -> error $ "No value for capture: " <> Text.unpack p
+  where
+    rec a rest = Text.encodeUtf8 a <> "/" <> pathToString captureValues rest
+
+httpSend :: forall media a. (FromReqBody media a) => Path -> Client a
+httpSend path =
+  mapRequest (addRequestHeader ("Accept", renderHeader $ toMediaType @media)) $
+    Client $ \config captureValues req ->
+      toSend <$> Http.httpLbs (setRoute captureValues path $ setPort config.port req) config.manager
+  where
+    toSend :: Http.Response BL.ByteString -> RespOr AnyMedia BL.ByteString a
+    toSend resp =
+      RespOr $ case fromReqBody @media $ Http.responseBody resp of
+        Right body -> Right $ toResp body
+        Left _ -> Left $ toResp $ Http.responseBody resp
+      where
+        toResp :: val -> Resp AnyMedia val
+        toResp = Resp (Http.responseStatus resp) (Http.responseHeaders resp) . Just
+
+setPort :: Int -> Http.Request -> Http.Request
+setPort port req = req{Http.port = port}
+
+setRoute :: CaptureMap -> Path -> Http.Request -> Http.Request
+setRoute captureValues path req = req{Http.path = pathToString captureValues path}
+
+----------------------------------------------------------
+-- from response
+
+-- | Helper type-synonym for convenience
+type ClientOr a = Client' (Either BL.ByteString a)
+
+{-| ClientConfig in @ReaderT IO@ monad. It encapsulates typical execution
+of client functions
+-}
+newtype Client' a = Client' (ReaderT ClientConfig IO a)
+  deriving (Functor, Applicative, Monad, MonadReader ClientConfig, MonadIO)
+
+-- | Runs the client call
+runClient' :: ClientConfig -> Client' a -> IO a
+runClient' config (Client' act) = runReaderT act config
+
+{-| Class to strip away all newtype wrappers that serve for API-definition.
+For example it converts the types signature for client function:
+
+> Capture "foo" Text -> Header "bar" Int -> Get Client (Resp a)
+
+to the version without HTTP-newtype wrappers:
+
+> Text -> Int -> Client' (Resp a)
+
+The instances are defined for all HTTP-newtype wrappers.
+Also we can use function @getRespOrValue@ if we do not need
+the http information of response.
+-}
+class FromClient a where
+  type ClientResult a :: Type
+  fromClient :: a -> ClientResult a
+
+instance (ToRespBody (RespMedia a) (RespError a), IsResp a) => FromClient (Send method Client a) where
+  type ClientResult (Send method Client a) = Client' (RespOr (RespMedia a) BL.ByteString (RespBody a))
+  fromClient f = Client' $ ReaderT $ flip fromSendClient f
+
+instance (FromClient b) => FromClient (Body media a -> b) where
+  type ClientResult (Body media a -> b) = a -> ClientResult b
+  fromClient f arg = fromClient (f (Body arg))
+
+instance (FromClient b) => FromClient (Capture sym a -> b) where
+  type ClientResult (Capture sym a -> b) = a -> ClientResult b
+  fromClient f arg = fromClient (f (Capture arg))
+
+instance (FromClient b) => FromClient (Query sym a -> b) where
+  type ClientResult (Query sym a -> b) = a -> ClientResult b
+  fromClient f arg = fromClient (f (Query arg))
+
+instance (FromClient b) => FromClient (QueryFlag a -> b) where
+  type ClientResult (QueryFlag a -> b) = Bool -> ClientResult b
+  fromClient f arg = fromClient (f (QueryFlag arg))
+
+instance (FromClient b) => FromClient (Optional sym a -> b) where
+  type ClientResult (Optional sym a -> b) = Maybe a -> ClientResult b
+  fromClient f arg = fromClient (f (Optional arg))
+
+instance (FromClient b) => FromClient (Header sym a -> b) where
+  type ClientResult (Header sym a -> b) = a -> ClientResult b
+  fromClient f arg = fromClient (f (Header arg))
+
+instance (FromClient b) => FromClient (OptionalHeader sym a -> b) where
+  type ClientResult (OptionalHeader sym a -> b) = Maybe a -> ClientResult b
+  fromClient f arg = fromClient (f (OptionalHeader arg))
+
+instance (FromClient b) => FromClient (PathInfo -> b) where
+  type ClientResult (PathInfo -> b) = ClientResult b
+  fromClient f = fromClient @b (f $ PathInfo [])
+
+instance (FromClient b) => FromClient (IsSecure -> b) where
+  type ClientResult (IsSecure -> b) = ClientResult b
+  fromClient f = fromClient @b (f (IsSecure True))
+
+instance (FromClient b) => FromClient (RawRequest -> b) where
+  type ClientResult (RawRequest -> b) = ClientResult b
+  fromClient f = fromClient @b (f $ error "no request")
+
+instance (FromClient b) => FromClient (RawResponse -> b) where
+  type ClientResult (RawResponse -> b) = ClientResult b
+  fromClient f = fromClient @b (f $ error "no response")
+
+fromSendClient ::
+  forall a method.
+  (ToRespBody (RespMedia a) (RespError a), IsResp a) =>
+  ClientConfig ->
+  Send method Client a ->
+  IO (RespOr (RespMedia a) BL.ByteString (RespBody a))
+fromSendClient config (Send client) =
+  joinRespOr @a <$> runClient config client
+
+joinRespOr :: forall a. (ToRespBody (RespMedia a) (RespError a), IsResp a) => RespOr AnyMedia BL.ByteString a -> RespOr (RespMedia a) BL.ByteString (RespBody a)
+joinRespOr (RespOr eResp) = RespOr $ case eResp of
+  Right resp -> case resp.body of
+    Just result ->
+      if getStatus result == ok200
+        then Right $ Resp (getStatus result) (getHeaders result) (getRespBody result)
+        else Left $ Resp (getStatus result) (getHeaders result) (toRespBody @(RespMedia a) <$> getRespError result)
+    Nothing -> Right $ Resp resp.status resp.headers Nothing
+  Left resp -> Left $ Resp resp.status resp.headers resp.body
+
+{-| If we need only value from the server and not HTTP-info (status, or headers)
+we can omit that data with this function
+-}
+getRespOrValue :: RespOr media BL.ByteString a -> Either BL.ByteString a
+getRespOrValue (RespOr eResp) = case eResp of
+  Right resp -> maybe noContentValue Right resp.body
+  Left resp -> maybe noContentValue Left resp.body
+  where
+    noContentValue = Left "No content in the response"
