2. Quick start

Writing your API service comprises of two steps

  • Writing a contract (schematic representation of your API)
  • Providing a server implementation

2.1. Contract

A contract is the list of end-points in your API service and the definition of each API endpoint. We define what goes in as request (Query params, form params, headers etc) and what comes out as the response of each API endpoint.

As an example, consider a API service that lets you create, update, delete and fetch users. First step is to create a datatype for our API service. Lets call it MyApiService

To define your contract using the framework, you need to

  • Declare a data type for your API service.
data MyApiService
  • Declare your routes as types.
type User   = Static "user"
type UserId = "user" :/ Int
  • Write a WebApi instance which declares the endpoints.
instance WebApi MyApiService where
  -- Route <Method>  <Route Name>
   type Apis MyApiService = '[ Route '[GET, POST]        User
                             , Route '[GET, PUT, DELETE] UserId
                             ]
  • Write ApiContract instances describing what goes in an request and what comes out as response from each API endpoint. Let’s write our first ApiContract instance for POST /user.
-- Our user type
data UserData = UserData { age     :: Int
                         , address :: Text
                         , name    :: Text
                         } deriving (Show, Eq, Generic)

data UserToken = UserToken { userId :: Text
                          , token :: Text
                          } deriving (Show, Eq, Generic)


-- Takes a User type in form params and returns UserToken.
instance ApiContract MyApiService POST User where
  type FormParam POST User = UserData
  type ApiOut    POST User = UserToken

In our code snippet above, the end-point POST /user takes user’s information (name, age and address) as post params and gives out the user’s token and userId

An equivalent curl syntax would be:

`curl -H "Content-Type: application/x-www-form-urlencoded" -d 'age=30&address=nazareth&name=Brian' http://api.peoplefrontofjudia.com/users `
  • Finally to complete our contract, we have to write instances for json, param serialization & deserialization for UserData and UserToken types. A definition needn’t be provided since GHC.Generics provides a generic implementation.
instance FromJSON UserData
instance ToJSON   UserData
instance FromParam 'FormParam UserData

{--We dont need a FromParam instance since UserToken according
 to our example is not sent us form params or query params -}
instance FromJSON UserToken
instance ToJSON   UserToken

This completes the contract part of the API.

2.2. Server implementation

  • First step is to create a type for the implementation and define WebApiServer instance for it.
data MyApiServiceImpl = MyApiServiceImpl

instance WebApiServer MyApiServiceImpl where
  type HandlerM MyApiServiceImpl = IO
  type ApiInterface MyApiServiceImpl = MyApiService

HandlerM is the base monad in which the handler will run. We also state that MyApiServiceImpl is the implementation for the contract MyApiServiceApi.

By keeping the implementation separate from the contract, it is possible for a contract to have multiple implementations.

  • Now let’s create the ApiHandler for one of our end-point POST /user
instance ApiHandler MyApiServiceImpl POST User where
  handler _ req = do
    let _userInfo = formParam req
    respond (UserToken "Foo" "Bar")

The last thing that is left is to create a WAI application from all the aforementioned information. For that we use serverApp .

myApiApp :: Wai.Application
myApiApp = serverApp serverSettings MyApiServiceImpl

main :: IO ()
main = run 8000 myApiApp

That’s it - now myApiApp could be run like any other WAI application.

There’s more you could do with WebApi apart from building API services. You can also build haskell clients for existing API services by defining just the contract, build full-stack webapps that serve html & javascript and generate mock servers.