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
andUserToken
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.