diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,30 @@
+Copyright An Long (c) 2021
+
+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,100 @@
+# request
+
+![](https://miro.medium.com/max/1200/1*5KglaZoNp4fNpNHUao5u5w.jpeg)
+
+HTTP client for haskell, inpired by [requests](https://requests.readthedocs.io/) and [http-dispatch](https://github.com/owainlewis/http-dispatch).
+
+## Usage
+
+You can try this in haskell REPL once you have `request` installed:
+
+```haskell
+import Network.HTTP.Request
+
+resp <- get "https://api.leancloud.cn/1.1/date"
+print $ requestStatus resp
+```
+
+## Core API
+
+Request's API has three core concepts: `Request` record type, `Response` record type, `send` function.
+
+### Request
+
+`Request` is all about the information you will send to the target URL.
+
+```haskell
+data Request = Request
+  { requestMethod  :: Method
+  , requestUrl     :: String
+  , requestHeaders :: Headers
+  , requestBody    :: Maybe Data.ByteString.ByteString
+  } deriving (Show)
+```
+
+### send
+
+Once you have constructed your own `Request` record, you can call the `send` function to send it to the server. The `send` function's type is:
+
+```haskell
+send :: Request -> IO Response
+```
+
+### Response
+
+`Response` is what you got from the server URL.
+
+```haskell
+data Response = Response
+  { responseStatus  :: Int
+  , responseHeaders :: Headers
+  , responseBody    :: Data.ByteString.ByteString
+  } deriving (Show)
+```
+
+### Example
+
+```haskell
+:set -XOverloadedStrings
+
+import Network.HTTP.Request
+
+-- Construct a Request record.
+let req = Request GET "https://api.leancloud.cn/1.1/date" [] Nothing
+-- Send it.
+res <- send req
+-- access the fields on Response.
+print $ requestStatus resp
+```
+
+## Shortcuts
+
+As you expected, there are some shortcuts for the most used scenarios.
+
+```haskell
+get :: String -> IO Response
+get url =
+  send $ Request GET url [] Nothing
+
+delete :: String -> IO Response
+delete url =
+  send $ Request DELETE url [] Nothing
+
+post :: (String, Maybe Data.ByteString.ByteString) -> IO Response
+post (url, body) =
+  send $ Request POST url [] body
+
+put :: (String, Maybe Data.ByteString.ByteString) -> IO Response
+put (url, body) =
+  send $ Request PUT url [] body
+```
+
+These shortcuts' definitions are simple and direct. You are encouraged to add your own if the built-in does not match your use cases, like add custom headers in every request.
+
+## About the Project
+
+Request is &copy; 2020-2021 by [aisk](https://github.com/aisk).
+
+### License
+
+Request is distributed by a [BSD license](https://github.com/aisk/request/tree/master/LICENSE).
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/request.cabal b/request.cabal
new file mode 100644
--- /dev/null
+++ b/request.cabal
@@ -0,0 +1,29 @@
+name:                request
+version:             0.1.0.0
+-- synopsis:
+description:         "HTTP client for haskell, inpired by requests and http-dispatch."
+homepage:            https://github.com/aisk/request#readme
+license:             BSD3
+license-file:        LICENSE
+author:              An Long
+maintainer:          aisk1988@gmail.com
+copyright:           2020 An Long
+category:            Web
+build-type:          Simple
+extra-source-files:  README.md
+cabal-version:       >=1.10
+
+library
+  hs-source-dirs:      src
+  exposed-modules:     Network.HTTP.Request
+  build-depends:       base               >= 4.7 && < 5
+                     , bytestring         >= 0.10.12 && < 0.11
+                     , case-insensitive   >= 1.2.1 && < 1.3
+                     , http-client        >= 0.7.7 && < 0.8
+                     , http-types         >= 0.12.3 && < 0.13
+                     , http-client-tls    >= 0.3.5 && < 0.4
+  default-language:    Haskell2010
+
+source-repository head
+  type:     git
+  location: https://github.com/aisk/request
diff --git a/src/Network/HTTP/Request.hs b/src/Network/HTTP/Request.hs
new file mode 100644
--- /dev/null
+++ b/src/Network/HTTP/Request.hs
@@ -0,0 +1,93 @@
+{-# LANGUAGE DatatypeContexts #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Network.HTTP.Request
+  ( Request
+  , Response
+  , Header
+  , Headers
+  ) where
+
+import qualified Data.String               as S
+import qualified Data.ByteString           as BS
+import qualified Data.ByteString.Char8     as C
+import qualified Data.ByteString.Lazy      as LBS
+import qualified Data.CaseInsensitive      as CI
+import qualified Data.List                 as List
+import qualified Network.HTTP.Client       as LowLevelClient
+import qualified Network.HTTP.Client.TLS   as LowLevelTLSClient
+import qualified Network.HTTP.Types.Status as LowLevelStatus
+
+type Header = (BS.ByteString, BS.ByteString)
+
+type Headers = [Header]
+
+data Method
+  = DELETE
+  | GET
+  | HEAD
+  | OPTIONS
+  | PATCH
+  | POST
+  | PUT
+  | TRACE
+  deriving (Eq, Show)
+
+data (S.IsString a) => Request a = Request
+  { requestMethod  :: Method
+  , requestUrl     :: String
+  , requestHeaders :: Headers
+  , requestBody    :: Maybe a
+  } deriving (Show)
+
+toLowlevelRequest :: (S.IsString a) => Request a -> IO LowLevelClient.Request
+toLowlevelRequest req = do
+  initReq <- LowLevelClient.parseRequest $ requestUrl req
+  return $ initReq { LowLevelClient.method = C.pack . show $ requestMethod req
+                   , LowLevelClient.requestHeaders = map (\(k, v) -> (CI.mk k, v)) $ requestHeaders req
+                   }
+
+data Response = Response
+  { responseStatus  :: Int
+  , responseHeaders :: Headers
+  , responseBody    :: BS.ByteString
+  } deriving (Show)
+
+fromLowLevelRequest :: LowLevelClient.Response LBS.ByteString -> Response
+fromLowLevelRequest res =
+  let status = LowLevelStatus.statusCode . LowLevelClient.responseStatus $ res
+      body = LBS.toStrict $ LowLevelClient.responseBody res
+      headers = LowLevelClient.responseHeaders res
+  in
+  Response status (map (\(k,v) ->
+                         let hk = CI.original k
+                         in
+                         (hk, v)) headers) body
+
+getManagerForUrl :: String -> IO LowLevelClient.Manager
+getManagerForUrl url =
+    if "https" `List.isPrefixOf` url then LowLevelClient.newManager LowLevelTLSClient.tlsManagerSettings
+                                     else LowLevelClient.newManager LowLevelClient.defaultManagerSettings
+
+send :: (S.IsString a) => Request a -> IO Response
+send req = do
+  manager <- getManagerForUrl $ requestUrl req
+  llreq <- toLowlevelRequest req
+  llres <- LowLevelClient.httpLbs llreq manager
+  return $ fromLowLevelRequest llres
+
+get :: String -> IO Response
+get url =
+  send $ Request GET url [] Nothing
+
+delete :: String -> IO Response
+delete url =
+  send $ Request DELETE url [] Nothing
+
+post :: (String, Maybe BS.ByteString) -> IO Response
+post (url, body) =
+  send $ Request POST url [] body
+
+put :: (String, Maybe BS.ByteString) -> IO Response
+put (url, body) =
+  send $ Request PUT url [] body
