# JSON and Web Services — Haskell

Source: https://www.geekswithgeeks.com/en/haskell/w-web

> Encode JSON with aeson and build a small HTTP API.

## Haskell on the server

**aeson** is the standard JSON library: types implement **`ToJSON`** and **`FromJSON`**, usually derived via **`Generic`** (`deriving (Generic, ToJSON, FromJSON)` with `DeriveGeneric` and `DeriveAnyClass`), or written by hand for custom formats; `encode` produces a lazy `ByteString` and `eitherDecode` returns an error message on bad input. For web services there are several styles: **Scotty** is a small, Sinatra-like framework, good for learning and simple APIs; **Servant** describes an API as a **type**, from which it derives the server, type-safe clients and OpenAPI documentation, so the compiler checks that handlers match the specification; **IHP** and **Yesod** are full-stack frameworks. Most run on the high-performance **Warp** server and the **WAI** interface. Database libraries include **persistent**, **postgresql-simple**, **hasql** and **beam**. A typical service keeps validated domain types and pure business logic in a library, uses a `ReaderT Env IO` application monad for handlers, and uses `aeson` types at the boundary.

## A JSON API with Scotty and aeson

Generic JSON instances and two routes.

```haskell
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE DeriveGeneric, DeriveAnyClass #-}
import Control.Monad.IO.Class (liftIO)
import Data.Aeson (FromJSON, ToJSON)
import Data.IORef (atomicModifyIORef', newIORef, readIORef)
import GHC.Generics (Generic)
import Network.HTTP.Types.Status (status201, status400)
import Web.Scotty

data Product = Product { sku :: String, pricePaise :: Int }
  deriving (Show, Generic, ToJSON, FromJSON)

main :: IO ()
main = do
  store <- newIORef [Product "pen" 4950, Product "ink" 19900]
  scotty 3000 $ do
    get "/products" $ do
      products <- liftIO (readIORef store)
      json products

    post "/products" $ do
      p <- jsonData :: ActionM Product        -- malformed JSON is rejected with an error response
      if pricePaise p <= 0
        then status status400 >> json ("price must be positive" :: String)
        else do
          liftIO (atomicModifyIORef' store (\ps -> (p : ps, ())))
          status status201
          json p

-- curl -X POST localhost:3000/products -d '{"sku":"pad","pricePaise":9900}' 
```

## Consider Servant for larger APIs

With Servant, the API type is the single source of truth: change a route's type and every handler and client that disagrees fails to compile, and documentation is generated from the same type.

**Quiz:** How are aeson JSON instances most commonly created for record types?

- [ ] They cannot be created
- [ ] By writing JSON strings manually
- [x] Derived via Generic with deriving (Generic, ToJSON, FromJSON)
- [ ] From a database schema

*Answer:* Derived via Generic with deriving (Generic, ToJSON, FromJSON). Generic deriving creates instances that follow the record's field names.
