diff --git a/LICENSE b/LICENSE
--- a/LICENSE
+++ b/LICENSE
@@ -1,30 +1,26 @@
-Copyright AN Long (c) 2021
-
-All rights reserved.
+Copyright (c) 2017, AN Long
 
 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.
+1. 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.
+2. 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.
+3. Neither the name of the copyright holder nor the names of its
+   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
+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 HOLDER 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
--- a/README.md
+++ b/README.md
@@ -4,40 +4,32 @@
 
 HTTP client for haskell, inpired by [requests](https://requests.readthedocs.io/) and [http-dispatch](https://github.com/owainlewis/http-dispatch).
 
+[![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/aisk/request)
+
 ## Installation
 
 This pacakge is published on [hackage](http://hackage.haskell.org/package/request) with the same name `request`, you can install it with cabal or stack or nix as any other hackage packages.
 
 ## 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 $ responseStatus resp
-```
-
-## Record Dot Syntax Support
-
-This library supports modern Haskell record dot syntax. To use it, enable these language extensions:
+This library supports modern Haskell record dot syntax. First, enable these language extensions:
 
 ```haskell
 {-# LANGUAGE DuplicateRecordFields #-}
 {-# LANGUAGE OverloadedRecordDot #-}
 ```
 
-### Creating Records with Dot Syntax
+Then you can use the library like this:
 
 ```haskell
-{-# LANGUAGE DuplicateRecordFields #-}
-{-# LANGUAGE OverloadedRecordDot #-}
-
 import Network.HTTP.Request
 import qualified Data.ByteString as BS
 
--- Create request using record dot syntax
+-- Using shortcuts
+resp <- get "https://api.leancloud.cn/1.1/date"
+print resp.status        -- 200
+
+-- Or construct a Request manually
 let req = Request { method = GET, url = "https://api.leancloud.cn/1.1/date", headers = [], body = Nothing }
 
 -- Response with ByteString body
@@ -59,11 +51,11 @@
 `Request` is all about the information you will send to the target URL.
 
 ```haskell
-data Request a = Request
+data Request = Request
   { method  :: Method
   , url     :: String
   , headers :: Headers
-  , body    :: Maybe a
+  , body    :: Maybe BS.ByteString
   } deriving (Show)
 ```
 
@@ -72,7 +64,7 @@
 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 :: (IsString a) => Request a -> IO (Response a)
+send :: (FromResponseBody a) => Request -> IO (Response a)
 ```
 
 ### Response
@@ -87,27 +79,24 @@
   } deriving (Show)
 ```
 
-The response body type `a` can be any type that implements the `IsString` constraint, allowing flexible handling of response data.
+The response body type `a` can be any type that implements the `FromResponseBody` constraint, allowing flexible handling of response data.
 
-### Backward Compatibility
+### Without Language Extensions
 
-For users who prefer not to use the language extensions, you can still:
+If you prefer not to use the language extensions, you can still use the library with the traditional syntax:
 
 - Create requests using positional arguments: `Request GET "url" [] Nothing`
-- Use prefixed accessor functions: `requestStatus response`, `requestHeaders response`, etc.
-
-### Example
+- Use prefixed accessor functions: `responseStatus response`, `responseHeaders response`, etc.
 
 ```haskell
-{-# LANGUAGE OverloadedStrings #-}
 
 import Network.HTTP.Request
 
--- Construct a Request record.
+-- Construct a Request using positional arguments
 let req = Request GET "https://api.leancloud.cn/1.1/date" [] Nothing
--- Send it.
+-- Send it
 res <- send req
--- Access the fields on Response.
+-- Access the fields using prefixed accessor functions
 print $ responseStatus res
 ```
 
@@ -116,21 +105,25 @@
 As you expected, there are some shortcuts for the most used scenarios.
 
 ```haskell
-get :: String -> IO (Response BS.ByteString)
+get :: (FromResponseBody a) => String -> IO (Response a)
 get url =
-  send $ Request { method = GET, url = url, headers = [], body = Nothing }
+  send $ Request GET url [] Nothing
 
-delete :: String -> IO (Response BS.ByteString)
+delete :: (FromResponseBody a) => String -> IO (Response a)
 delete url =
-  send $ Request { method = DELETE, url = url, headers = [], body = Nothing }
+  send $ Request DELETE url [] Nothing
 
-post :: (String, Maybe BS.ByteString) -> IO (Response BS.ByteString)
-post (url, body) =
-  send $ Request { method = POST, url = url, headers = [], body = body }
+post :: (FromResponseBody a) => String -> Maybe BS.ByteString -> IO (Response a)
+post url body =
+  send $ Request POST url [] body
 
-put :: (String, Maybe BS.ByteString) -> IO (Response BS.ByteString)
-put (url, body) =
-  send $ Request { method = PUT, url = url, headers = [], body = body }
+put :: (FromResponseBody a) => String -> Maybe BS.ByteString -> IO (Response a)
+put url body =
+  send $ Request PUT url [] body
+
+patch :: (FromResponseBody a) => String -> Maybe BS.ByteString -> IO (Response a)
+patch url body =
+  send $ Request PATCH 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.
@@ -141,7 +134,7 @@
 
 ## About the Project
 
-Request is &copy; 2020-2021 by [aisk](https://github.com/aisk).
+Request is &copy; 2020-2026 by [AN Long](https://github.com/aisk).
 
 ### License
 
diff --git a/request.cabal b/request.cabal
--- a/request.cabal
+++ b/request.cabal
@@ -1,5 +1,5 @@
 name:                request
-version:             0.3.1.0
+version:             0.4.0.0
 -- synopsis:
 description:         "HTTP client for haskell, inpired by requests and http-dispatch."
 homepage:            https://github.com/aisk/request#readme
@@ -22,6 +22,7 @@
                      , http-client        >= 0.6.4 && < 0.8
                      , http-types         >= 0.12.3 && < 0.13
                      , http-client-tls    >= 0.3.5 && < 0.4
+                     , text               >= 1.2.4 && < 2.2
   default-language:    Haskell2010
 
 source-repository head
@@ -36,4 +37,5 @@
                      , request
                      , hspec
                      , bytestring         >= 0.10.12 && < 0.13
+                     , text               >= 1.2.4 && < 2.2
   default-language:    Haskell2010
diff --git a/src/Network/HTTP/Request.hs b/src/Network/HTTP/Request.hs
--- a/src/Network/HTTP/Request.hs
+++ b/src/Network/HTTP/Request.hs
@@ -1,14 +1,17 @@
 {-# LANGUAGE DuplicateRecordFields #-}
+{-# LANGUAGE FlexibleInstances #-}
 {-# LANGUAGE OverloadedRecordDot #-}
 {-# LANGUAGE OverloadedStrings #-}
 
 module Network.HTTP.Request
   ( Header,
     Headers,
+    FromResponseBody (..),
     Method (..),
     Request (..),
     Response (..),
     get,
+    delete,
     patch,
     post,
     put,
@@ -27,8 +30,8 @@
 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 Data.String as S
+import qualified Data.Text as T
+import qualified Data.Text.Encoding as T
 import qualified Network.HTTP.Client as LowLevelClient
 import qualified Network.HTTP.Client.TLS as LowLevelTLSClient
 import qualified Network.HTTP.Types.Status as LowLevelStatus
@@ -37,6 +40,21 @@
 
 type Headers = [Header]
 
+class FromResponseBody a where
+  fromResponseBody :: LBS.ByteString -> a
+
+instance FromResponseBody BS.ByteString where
+  fromResponseBody = LBS.toStrict
+
+instance FromResponseBody LBS.ByteString where
+  fromResponseBody = id
+
+instance FromResponseBody T.Text where
+  fromResponseBody = T.decodeUtf8Lenient . LBS.toStrict
+
+instance FromResponseBody String where
+  fromResponseBody = T.unpack . T.decodeUtf8Lenient . LBS.toStrict
+
 data Method
   = DELETE
   | GET
@@ -47,17 +65,7 @@
   | PUT
   | TRACE
   | Method String
-
-instance Show Method where
-  show DELETE = "DELETE"
-  show GET = "GET"
-  show HEAD = "HEAD"
-  show OPTIONS = "OPTIONS"
-  show PATCH = "PATCH"
-  show POST = "POST"
-  show PUT = "PUT"
-  show TRACE = "TRACE"
-  show (Method method) = method
+  deriving (Show, Eq)
 
 data Request = Request
   { method :: Method,
@@ -107,10 +115,10 @@
 responseBody :: Response a -> a
 responseBody res = res.body
 
-fromLowLevelRequest :: (S.IsString a) => LowLevelClient.Response LBS.ByteString -> Response a
-fromLowLevelRequest res =
+fromLowLevelResponse :: (FromResponseBody a) => LowLevelClient.Response LBS.ByteString -> Response a
+fromLowLevelResponse res =
   let status = LowLevelStatus.statusCode . LowLevelClient.responseStatus $ res
-      body = S.fromString . C.unpack . LBS.toStrict $ LowLevelClient.responseBody res
+      body = fromResponseBody $ LowLevelClient.responseBody res
       headers = LowLevelClient.responseHeaders res
    in Response
         status
@@ -123,29 +131,29 @@
         )
         body
 
-send :: (S.IsString a) => Request -> IO (Response a)
+send :: (FromResponseBody a) => Request -> IO (Response a)
 send req = do
   manager <- LowLevelTLSClient.getGlobalManager
   llreq <- toLowlevelRequest req
   llres <- LowLevelClient.httpLbs llreq manager
-  return $ fromLowLevelRequest llres
+  return $ fromLowLevelResponse llres
 
-get :: String -> IO (Response BS.ByteString)
+get :: (FromResponseBody a) => String -> IO (Response a)
 get url =
   send $ Request GET url [] Nothing
 
-delete :: String -> IO (Response BS.ByteString)
+delete :: (FromResponseBody a) => String -> IO (Response a)
 delete url =
   send $ Request DELETE url [] Nothing
 
-post :: (String, Maybe BS.ByteString) -> IO (Response BS.ByteString)
-post (url, body) =
+post :: (FromResponseBody a) => String -> Maybe BS.ByteString -> IO (Response a)
+post url body =
   send $ Request POST url [] body
 
-put :: (String, Maybe BS.ByteString) -> IO (Response BS.ByteString)
-put (url, body) =
+put :: (FromResponseBody a) => String -> Maybe BS.ByteString -> IO (Response a)
+put url body =
   send $ Request PUT url [] body
 
-patch :: (String, Maybe BS.ByteString) -> IO (Response BS.ByteString)
-patch (url, body) =
+patch :: (FromResponseBody a) => String -> Maybe BS.ByteString -> IO (Response a)
+patch url body =
   send $ Request PATCH url [] body
diff --git a/test/Spec.hs b/test/Spec.hs
--- a/test/Spec.hs
+++ b/test/Spec.hs
@@ -4,40 +4,43 @@
 
 module Main where
 
+import Data.List (isInfixOf)
 import Network.HTTP.Request
 import Test.Hspec
 import qualified Data.ByteString as BS
+import qualified Data.Text as T
+import qualified Data.Text.Encoding as T
 
 main :: IO ()
 main = hspec $ do
   describe "Network.HTTP.Request" $ do
     it "should fetch example.com and return 200 OK" $ do
-      response <- get "http://example.com"
+      response <- get "http://example.com" :: IO (Response String)
       responseStatus response `shouldBe` 200
 
     it "should send a request to example.com and return 200 OK" $ do
-      response <- send (Request GET "http://example.com" [] Nothing)
+      response <- send (Request GET "http://example.com" [] Nothing) :: IO (Response String)
       responseStatus response `shouldBe` 200
 
-    it "should post to httpbin.org/post and return 200 OK" $ do
-      response <- post ("https://postman-echo.com/post", Just "Hello!")
+    it "should post to postman-echo.com/post and return 200 OK" $ do
+      response <- post "https://postman-echo.com/post" (Just "Hello!") :: IO (Response String)
       responseStatus response `shouldBe` 200
 
-    it "should put to httpbin.org/put and return 200 OK" $ do
-      response <- put ("https://postman-echo.com/put", Just "Hello!")
+    it "should put to postman-echo.com/put and return 200 OK" $ do
+      response <- put "https://postman-echo.com/put" (Just "Hello!") :: IO (Response String)
       responseStatus response `shouldBe` 200
 
-    it "should patch to httpbin.org/patch and return 200 OK" $ do
-      response <- patch ("https://postman-echo.com/patch", Just "Hello!")
+    it "should patch to postman-echo.com/patch and return 200 OK" $ do
+      response <- patch "https://postman-echo.com/patch" (Just "Hello!") :: IO (Response String)
       responseStatus response `shouldBe` 200
 
     it "should use dot record syntax to create and access request/response" $ do
       let req = Request { method = GET, url = "http://example.com", headers = [("User-Agent", "Haskell-Request")], body = Nothing }
-      response <- send req
+      response <- send req :: IO (Response String)
       response.status `shouldBe` 200
       response.headers `shouldSatisfy` (not . null)
 
-    it "should access response body with different IsString types" $ do
+    it "should access response body with different types" $ do
       -- Test with ByteString body
       let req1 = Request { method = GET, url = "http://example.com", headers = [], body = Nothing }
       response1 <- send req1 :: IO (Response BS.ByteString)
@@ -47,3 +50,27 @@
       let req2 = Request { method = GET, url = "http://example.com", headers = [], body = Nothing }
       response2 <- send req2 :: IO (Response String)
       not (null response2.body) `shouldBe` True
+
+    it "should correctly handle UTF-8 encoded response (Chinese characters)" $ do
+      let msg = "{\"message\":\"你好世界\"}"
+      let req = Request
+            { method = POST
+            , url = "https://postman-echo.com/post"
+            , headers = [("Content-Type", "application/json; charset=utf-8")]
+            , body = Just (T.encodeUtf8 $ T.pack msg)
+            }
+      response <- send req
+      responseStatus response `shouldBe` 200
+      responseBody response `shouldSatisfy` isInfixOf "你好世界"
+
+    it "should correctly handle UTF-8 encoded response (emoji)" $ do
+      let msg = "{\"message\":\"Hello 🌍\"}"
+      let req = Request
+            { method = POST
+            , url = "https://postman-echo.com/post"
+            , headers = [("Content-Type", "application/json; charset=utf-8")]
+            , body = Just (T.encodeUtf8 $ T.pack msg)
+            }
+      response <- send req
+      responseStatus response `shouldBe` 200
+      responseBody response `shouldSatisfy` isInfixOf "🌍"
