Guides

Servant integration

Okapi and Servant make a great combo. Since Okapi is built directly on top of wai, you can embed your Okapi application into Servant easily. Here's how.


  1. Define your Servant API type and handlers as usual:

    type ServantAPI = Get '[JSON] MyData
    
    servantHandler :: Server ServantAPI
    servantHandler = pure MyData
    
  2. Turn your Okapi API into an Application using makeOkapiApp:

    okapiApp :: Application
    okapiApp = makeOkapiApp id okapiAPI
    
    okapiAPI :: OkapiT IO Response
    okapiAPI = do
      ...
    
  3. Wrap your Okapi app with the Tagged data constructor from Data.Tagged and annotate it with the type Tagged Handler Application:

    taggedOkapiApp :: Tagged Handler Application
    taggedOkapiApp = Tagged okapiApp
    
  4. Add the Raw type from Servant.API.Raw to your Servant API type and create a Proxy value for it:

    type API = ServantAPI :<|> Raw
    
    apiProxy :: Proxy API
    apiProxy = Proxy
    
  5. Add your tagged Okapi app to the Servant handler in the same spot where Raw is in the API type:

    apiHandler :: Server API
    apiHandler = servantHandler :<|> taggedOkapiApp
    
  6. Serve your Servant app as usual:

    main :: IO ()
    main = do
        print "Running app on port 8080"
        run 8080 $ serve apiProxy apiHandler
    

All together, it should look something like this:

main :: IO ()
main = do
    print "Running app on port 8080"
    run 8080 $ serve apiProxy apiHandler

-- Servant + Okapi
type API = ServantAPI :<|> Raw

apiHandler :: Server API
apiHandler = servantHandler :<|> taggedOkapiApp

apiProxy :: Proxy API
apiProxy = Proxy

-- Servant
type ServantAPI = Get '[JSON] MyData

servantHandler :: Server ServantAPI
servantHandler = pure MyData

-- Okapi
taggedOkapiApp :: Tagged Handler Application
taggedOkapiApp = Tagged okapiApp

okapiApp :: Application
okapiApp = makeOkapiApp id okapiAPI

okapiAPI :: OkapiT IO Response
okapiAPI = do
  ...

You can find a complete example here.

Warning

Beware if you are routing to your Okapi app with :> like so:

apiHandler :: Server API
apiHandler =
  servantHandler
  :<|> ("blah" :> "okapi" :> taggedOkapiApp)

This will strip away pathInfo as mentioned in the Haddock documentation for Servant.API.Raw, so adjust your Okapi app accordingly.

If your Okapi app is defined as:

okapiAPI = do
    methodGET
    pathParam `is` "blah"
    pathParam `is` "okapi"
    ...

Servant will parse the path segments blah and okapi before passing the request to your Okapi app, so you only need to have:

okapiAPI = do
    methodGET
    ...

According to the docs, only the request path is affected.

Gradually Typing Your APIs

The ability to easily embed your Okapi APIs into Servant makes it suitable as quick prototyping tool. You can start of with an API that is mostly implemented in Okapi, which is easier to use and modify quickly, but isn't as type safe:

type API = TypedEndpoint1 :<|> Raw

apiHandler :: Server API
apiHandler = typedEndpoint1Handler :<|> taggedBigOkapiApp

And gradually move more endpoints from your Okapi API into your Servant API:

type API =
    TypedEndpoint1
    :<|> TypedEndpoint2
    :<|> TypedEndpoint3
    :<|> TypedEndpoint4
    :<|> TypedEndpoint5
    :<|> Raw

apiHandler :: Server API
apiHandler =
    typedEndpoint1Handler
    :<|> typedEndpoint2Handler
    :<|> typedEndpoint3Handler
    :<|> typedEndpoint4Handler
    :<|> typedEndpoint5Handler
    :<|> taggedSmallerOkapiApp
Previous
WAI integration