Haskell Servant 入門 (Hello world)

# Hello world in Haskell with Servant

## 連載一覧
Haskell Servant フレームワークの便利な使い方などを紹介する連載記事です。
今後は以下に記事を追加していきます。
(2016年7月22日 6つ目の記事を追加しました！)

1. [Hello World](http://qiita.com/algas/items/c76df85292e4e3fb83fd)
2. [Logger](http://qiita.com/algas/items/9f409ec0c43033c1e52c)
3. [HTTPS](http://qiita.com/algas/items/a9d1527e4a3f318dc09d)
4. [Testing](http://qiita.com/algas/items/3f7029c92824392a0b11)
5. [Database](http://qiita.com/algas/items/5a6e570028d95b5dac1b)
6. [Strongly Typing](http://qiita.com/algas/items/343f138b9eef25168155)

## 概要

本記事では Haskell Servant の導入方法をご紹介します。

記事内のすべてのコードは github リポジトリに置いてあります。
http://github.com/algas/haskell-servant-cookbook

## 想定する読者

- Haskell の入門書を読んだことがある
- 言語を問わず Web アプリケーションを触ったことがある
- RDB (Relational Database) の基本的な知識がある

Haskell や Web フレームワーク自体について本記事では詳しく説明しません。

## Haskell Servant とは？
Haskell の Web フレームワークで、以下の特徴があります。

- 機能は少なめでシンプル(フルスタックではない)
- 型による強い制限を持つ
- Client と Server を同じ API から生成できる

## プロジェクト作成
haskell-stack がインストールできているものとします。
Terminal で下記のコマンドを実行すると、myproject ディレクトリに servant プロジェクトの雛形が作成されます。

```bash
$ stack new myproject servant
```

## API
"/" で Hello World を返し、
"/users/(name)/(age)" で User を返すシンプルな API を定義します。
User 型は name, age という属性を持っています。

```hs:lib/HelloApi.hs
{-# LANGUAGE DataKinds         #-}
{-# LANGUAGE DeriveGeneric     #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeOperators     #-}
module HelloApi where

import           Data.Aeson
import           Data.Proxy
import           Data.Text                  (Text)
import qualified Data.Text                  as T
import           GHC.Generics
import           Servant.API

data User = User
    { name :: Text
    , age  :: Int
    } deriving (Eq, Show, Read, Generic)
instance FromJSON User
instance ToJSON User

type HelloAPI  = Get '[PlainText] Text
            :<|> "user" :> Capture "name" Text :> Capture "age" Int :> Get '[JSON] User

helloApi :: Proxy HelloAPI
helloApi = Proxy
```

## Server
HelloApi を呼び出し、サーバとして機能させます。

```hs:server/Main.hs
{-# LANGUAGE OverloadedStrings #-}
module Main where

import           HelloApi
import           Network.Wai
import           Network.Wai.Handler.Warp
import           Servant
import           Servant.API

server :: Server HelloAPI
server = hello :<|> user
    where
        hello = return "Hello world"
        user n a = return $ User n a

app :: Application
app = serve helloApi server

main :: IO ()
main = do
    run 8080 app
```

## Client
HelloApi を呼び出し、クライアントとして機能させます。

```hs:client/Main.hs
{-# LANGUAGE OverloadedStrings #-}
module Main where

import           Control.Monad.Trans.Except (ExceptT, runExceptT)
import           Data.Text                  (Text)
import qualified Data.Text                  as T
import           HelloApi
import           Network.HTTP.Client        (Manager, defaultManagerSettings,
                                             newManager)
import           Servant.API
import           Servant.Client

hello :<|> user = client helloApi

queries :: Manager -> BaseUrl -> ExceptT ServantError IO (Text, User)
queries manager baseurl = do
    h <- hello manager baseurl
    us <- user "John Smith" 26 manager baseurl
    return (h, us)

main :: IO ()
main = do
    manager <- newManager defaultManagerSettings
    let baseUrl = BaseUrl Http "localhost" 8080 ""
    res <- runExceptT $ queries manager baseUrl
    case res of
        Left err -> putStrLn $ "Error: " ++ show err
        Right p -> do
            print p
```

## 動作確認

0. ビルドする  
`$ stack build`
0. サーバを起動する  
`$ stack exec hello-server`
0. curl でサーバにアクセスしてみる  
`$ curl http://localhost:8080`  
`Hello world`  
`$ curl http://localhost:8080/users/hoge/3`  
`{"age":3,"name":"hoge"}`
0. クライアントでサーバにアクセスする  
`$ stack exec hello-client`  
`("Hello world",User {name = "John Smith", age = 26})`

クライアントからサーバにアクセスして値を取得することができました。
サーバとクライアントで同じAPIを使っているので、冗長な記述を減らせるのが Servant のメリットの1つです。

## 動作環境
- stack:lts-6.0
- servant-0.7

## 参考文献
http://haskell-servant.readthedocs.io/en/stable/index.html

## 連載予定
Haskell Servant 入門シリーズを連載で執筆予定です。
お楽しみに。