MIT licensed by Michael Snoyman
Maintained by [email protected]
This version can be pinned in stack with:wai-3.2.4@sha256:e14fde953183f9db8c10da79b44a2b24d241c1203426c5636995069e2dacb1fe,1776

Module documentation for 3.2.4

WAI: Web Application Interface

Getting started

You want a minimal example? Here it is!

{-# LANGUAGE OverloadedStrings #-}
import Network.Wai
import Network.HTTP.Types
import Network.Wai.Handler.Warp (run)

app :: Application
app _ respond = do
    putStrLn "I've done some IO here"
    respond $ responseLBS
        [("Content-Type", "text/plain")]
        "Hello, Web!"

main :: IO ()
main = do
    putStrLn $ "http://localhost:8080/"
    run 8080 app

Put that code into a file named hello.hs and install wai and warp from Hackage:

cabal install wai warp

Run it:

runhaskell hello.hs

Point your browser to:


Serving static content

We can modify our previous example to serve static content. For this create a file named index.html:

<p>Hello, Web!</p>

Now we redefine responseBody to refer to that file:

app2 :: Application
app2 _ respond = respond index

index :: Response
index = responseFile
    [("Content-Type", "text/html")]

Basic dispatching

An Application maps Requests to Responses:

ghci> :info  Application
type Application = Request -> (Response -> IO ResponseReceived) -> IO ResponseReceived

Depending on the path info provided with each Request we can serve different Responses:

app3 :: Application
app3 request respond = respond $ case rawPathInfo request of
    "/"     -> index
    "/raw/" -> plainIndex
    _       -> notFound

plainIndex :: Response
plainIndex = responseFile
    [("Content-Type", "text/plain")]

notFound :: Response
notFound = responseLBS
    [("Content-Type", "text/plain")]
    "404 - Not Found"

Doing without overloaded strings

For the sake of efficiency, WAI uses the bytestring package. We used GHCs overloaded strings to almost hide this fact. But we can easily do without. What follows is a more verbose definition of notFound, that works without GHC extensions:

import qualified Data.ByteString.Char8 as B8
import qualified Data.ByteString.Lazy.Char8 as LB8
import           Data.CaseInsensitive (mk)

notFound = responseLBS
    [(mk $ B8.pack "Content-Type", B8.pack "text/plain")]
    (LB8.pack "404 - Not Found")


ChangeLog for wai


  • Add helpers for modifying request headers: modifyRequest and mapRequestHeaders. #710 #952
  • Small documentation adjustments like adding more @since markers. #952
  • Add setRequestBodyChunks to mirror getRequestBodyChunk and avoid deprecation warnings when using requestBody as a setter. #949
  • Overhaul documentation of Middleware. #858


  • Add documentation recommending streaming request bodies. #818
  • Add two functions, consumeRequestBodyStrict and consumeRequestBodyLazy, that are synonyms for strictRequestBody and lazyRequestBody. #818

  • Fix missing reexport of getRequestBodyChunk #753


  • Deprecate requestBody in favor of the more clearly named getRequestBodyChunk. #726

  • Remove dependency on blaze-builder #683

  • Relax upper bound on bytestring-builder


  • add mapResponseStatus #532

  • Add missing changelog entry


  • Major version up due to breaking changes. We chose 3.2.0, not 3.1.0 for consistency with Warp 3.2.0.
  • The Network.Wai.HTTP2 module was removed.
  • tryGetFileSize, hContentRange, hAcceptRanges, contentRangeHeader and chooseFilePart, adjustForFilePart and parseByteRanges were removed from the Network.Wai.Internal module.
  • New fields for Request: requestHeaderReferer and requestHeaderUserAgent.

  • Avoid using the IsString Builder instance

  • A new module Network.Wai.HTTP2 is exported.

  • mapResponseHeaders, ifRequest and modifyResponse are exported.

  • Allow blaze-builder 0.4