LoginSignup
11
4

More than 3 years have passed since last update.

HaskellとScottyで簡単にAPIを構築する

Posted at

はじめに

HaskellのWebフレームワークはYesod、Spockなどいくつかありますが、そのなかでも軽量なScottyを紹介します。
RubyのSinatraに影響を受けており、非常に簡単にWebサーバを作ることができます。

準備

以下のライブラリを使いますので、package.yamlなどに追加しておいてください。

- http-types
- aeson
- scotty

まずとても簡単な例

以下のようなコードを書いて実行してみましょう。たった5行でサーバを立てることができます。

Sample.hs
{-# LANGUAGE OverloadedStrings #-}
module Sample where
import Web.Scotty
main :: IO ()
main = scotty 3000 $ get "/" $ html "<h1>Hello</h1>"

http://localhost:3000/にアクセスしてみましょう。

ユーザのAPI

例としてユーザデータを参照/作成/削除をするAPIを実装してみます。簡単のためにDBなどは使わずにオンメモリにデータを保存することにします。

ユーザデータの定義

まず、JSONで扱えるユーザデータを定義します。また、参照/作成/削除の関数も実装しておきます。

data User = User { uid :: Integer, name :: String, age :: Integer } deriving (Generic, Show)
instance ToJSON User
instance FromJSON User

addUser :: [User] -> User -> [User]
addUser users user = user:users

deleteUser :: [User] -> Integer -> [User]
deleteUser users i = filter (\user -> uid user /= i) users

findUser :: [User] -> Integer -> Maybe User
findUser users i = find (\u -> uid u == i) users

エラーの定義

data Error = Error { message :: String } deriving (Generic, Show)
instance ToJSON Error
instance FromJSON Error

ユーザの簡易のリポジトリ

Listでユーザのリポジトリを作成します。今回はListの中身を変更できるようにIORefを使います。
IORefは例外的にミュータブルな(=変更可能な)変数を扱うためのモナドです。
readIORef[User]型データを読み込んだり、writeIORefで書き換えができます。

main = do
  users <- newIORef [] :: IO (IORef [User])

Scottyの起動

scotty関数を呼び出すことで起動します。次の例ではポート3000番でlistenします。

main = do
  -- (省略)
  scotty 3000 $ do

ユーザの取得

すべてのユーザを取得するAPIを作成します。
liftIOについてはこちらを参照してみてください。

get "/users" $ do
  us <- liftIO (readIORef users) -- リポジトリからすべてのユーザを取得
  status status200
  json us
呼び出し例
$ curl -X GET http://localhost:3000/users

さらにPathパラメータ:uidを利用して、特定のユーザを取得するAPIも作成します。

get "/users/:uid" $ do
  us <- liftIO (readIORef users)
  i <- param "uid" -- Pathパラメータ uid を取得
  case findUser us (read i) of -- 指定されたユーザが存在するか否かで場合分け
    Just u -> status status200 >> json u
    Nothing -> status status404 >> json (Error ("Not Found uid = " <> i))
呼び出し例
$ curl -X GET http://localhost:3000/users/1

ユーザの作成

ユーザを作成するAPIを作成します。

post "/users" $ do
  u <- jsonData -- BodyをJSONで取得
  us <- liftIO $ readIORef users
  liftIO $ writeIORef users $ addUser us u -- 新規ユーザが追加されたリストにリポジトリを書き換え

  status status201
  json u
呼び出し例
$ curl -X POST http://localhost:3000/users -d '{ "uid": 1, "name": "alice", "age": 20 }'

ユーザの削除

ユーザを削除するAPIを作成します。

delete "/users/:uid" $ do
  i <- param "uid"
  us <- liftIO $ readIORef users
  liftIO $ writeIORef users $ deleteUser us i
  status status204
呼び出し例
curl -v -X DELETE http://localhost:3000/users/1

完成コード

完成したコードは以下のようになります。Scottyを使うと割と簡単にAPIを記述できました!

UserAPI.hs
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE DeriveGeneric #-}

module UserAPI where

import Web.Scotty
import Data.Aeson (FromJSON, ToJSON)
import GHC.Generics
import Data.IORef
import Control.Monad.Reader
import Network.HTTP.Types.Status
import Data.List (find)

data User = User { uid :: Integer, name :: String, age :: Integer } deriving (Generic, Show)
instance ToJSON User
instance FromJSON User

data Error = Error { message :: String } deriving (Generic, Show)
instance ToJSON Error
instance FromJSON Error

addUser :: [User] -> User -> [User]
addUser users user = user:users

deleteUser :: [User] -> Integer -> [User]
deleteUser users i = filter (\user -> uid user /= i) users

findUser :: [User] -> Integer -> Maybe User
findUser users i = find (\u -> uid u == i) users

main :: IO ()
main = do
  users <- newIORef [] :: IO (IORef [User])
  scotty 3000 $ do
    get "/users" $ do
      us <- liftIO (readIORef users)
      status status200
      json us
    get "/users/:uid" $ do
      us <- liftIO (readIORef users)
      i <- param "uid"
      case findUser us (read i) of
        Just u -> status status200 >> json u
        Nothing -> status status404 >> json (Error ("Not Found uid = " <> i))
    post "/users" $ do
      u <- jsonData
      us <- liftIO $ readIORef users
      liftIO $ writeIORef users $ addUser us u
      status status201
      json u
    delete "/users/:uid" $ do
      i <- param "uid"
      us <- liftIO $ readIORef users
      liftIO $ writeIORef users $ deleteUser us i
      status status204
11
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
4