liveBook · Manning

Servant

As explained in Learn Haskell by Example

Servant is a Haskell library ecosystem for defining type-safe web APIs and generating server and client implementations from API descriptions.

Servant is a Haskell ecosystem for describing an HTTP API as a type, then checking a server implementation against that description. One API definition records routes, HTTP methods, request bodies, and response formats, keeping network details behind the library’s abstractions. The same description can also support generated clients and documentation. section 17.1 section 17.0

An API type is assembled with type operators. (:>) extends one endpoint description with a path segment, request requirement, or response; (:<|>) joins endpoint descriptions into one larger API. GET, POST, PATCH, DELETE, ReqBody, Capture, and response combinators express the HTTP-facing contract directly in the type. JSON responses use forms such as Get '[JSON] SomeType, while a request body can require ReqBody '[JSON] SomeType. A body that does not meet the declared specification is rejected automatically. section 17.1.1

Listing 17.1 A type for the todo list API server
{-# LANGUAGE TypeOperators #-}     #1
{-# LANGUAGE DataKinds #-}        #1
module Todos.Api where

import Servant.API
import Todos.Types

type TodoApi =
  "entries" :> Get '[JSON] [TodoEntry]        #2
    :<|> "add" :> ReqBody '[JSON] TodoEntry :>
 PostCreated '[JSON] Int                       #3
    :<|> "toggle" :> Capture "id" Int :> PatchNoContent    #4

A todo API combines a JSON listing endpoint, an endpoint that accepts a TodoEntry and returns its identifier, and an identifier-capturing toggle endpoint. section 17.1.1

A server implementation mirrors the API type with handlers. Handler represents the action for one endpoint; Server combines those actions for the complete specification. For example, a database action returning [TodoEntry] can become Handler [TodoEntry] through liftIO, leaving JSON conversion to Servant. Supplying database actions as handler arguments limits direct connection access and makes fake database actions easier to substitute. section 17.1.3

Handlers can translate application results into HTTP responses while keeping their type-shaped inputs and outputs:

  • An add handler receives a TodoEntry, runs a supplied insertion action, returns its identifier on success, and turns a SQLite error into an HTTP 422 response with an error body. section 17.1.3
  • A toggle handler receives an Int capture, finds and updates an entry, returns NoContent on success, and raises HTTP 404 when no entry exists. section 17.1.3
  • The server joins endpoint handlers with (:<|>), in the same order as the TodoApi declaration. section 17.1.3

Servant produces a WAI Application, so routes that should not belong to the machine-facing API can be added through Middleware. Middleware has type Application -> Application: it wraps an application, inspects a request, and either supplies a response or delegates to the wrapped application. This separates a root HTML todo-list page from the JSON API because a derived API client should not need to request that page. section 17.2.2

The servant-client package derives client actions from the same API type. client accepts a Proxy for the API and produces a ClientM action for each endpoint. Endpoint results become ClientM result types, while request bodies and captures become function arguments; JSON encoding is handled automatically. runClientM executes a ClientM action with a ClientEnv and yields either ClientError or a result in IO. section 17.3.1 section 17.4

Listing 17.13 Derivation of client functionality from the API’s Proxy
getEntries :: ClientM [TodoEntry]              #1
addEntry :: TodoEntry -> ClientM Int          #1
toggleEntry :: Int -> ClientM NoContent       #1
getEntries :<|> addEntry :<|> toggleEntry =
 client todoApi          #2

The generated client declaration exposes three actions whose argument and result types follow the entries, add, and toggle API endpoints. section 17.3.1

The practical payoff is a single API contract shared by server and client code. Define the route structure once, implement handlers matching it, use ServerError values for fitting failures, and derive client functions instead of hand-writing HTTP calls. section 17.4

How Learn Haskell by Example covers it

How deep it goes
Works through API operators, concrete handlers, HTTP errors, middleware, and generated clients, while deferring the internals of DataKinds and TypeOperators.
Examples
Builds a todo API with entries, add, and toggle endpoints, database-backed handlers, a root HTML route, and derived client actions.
What it assumes
Assumes existing JSON conversion, database storage, and todo data types while introducing Servant API declarations and handlers from examples.
What you can do after
Apply it via Servant to specify a typed HTTP API, assemble matching handlers, add middleware, and derive client functions.
How it connects
Connects HTTP, JSON, Handler, Server, WAI Application, Middleware, Proxy, ClientM, SQLite, and ServerError.

Also explained in

Compare how 2 books explain Servant →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Learn Haskell by Example. The section links point to the parts of the book it draws on.