昨日はcompojure
を使ってルーティングを設定しました。
今日はミドルウェアを使用してjsonを扱えるようにしていきます
Ring
でJsonを扱う際には ring-json
というライブラリが便利です
ring-json
まずは依存を追加します
:dependencies [[org.clojure/clojure "1.11.1"]
[...]
[ring/ring-json "0.5.1"]]
これで追加できました。
ring-jsonにはringでjsonを扱う際に便利なミドルウェアがいくつかありますが、
jsonレスポンスを返せるようにするためには wrap-json-response
を使います
wrap-json-response
公式ドキュメントにもあった例を実装してみます
(ns todo.core
(:require [ring.adapter.jetty :as jetty]
[ring.util.response :as res]
[ring.middleware.json :refer [wrap-json-response]]))
(defn handler [req]
(res/response {:foo "bar"}))
(def app
(wrap-json-response handler))
(defn -main []
(jetty/run-jetty app {:port 3000}))
これでサーバーを起動しリクエストを送ってみます
$ curl localhost:3000
{"foo":"bar"}
ring.util.response
にある response
という関数で response-body
を生成し、
wrap-json-body
のミドルウェアでhandlerをwrapすることでjsonレスポンスを返せるようになりました。
昨日作成したルーティングにも組み込んでみます
(ns todo.core
(:require [ring.adapter.jetty :as jetty]
[ring.util.response :as res]
[ring.middleware.json :refer [wrap-json-response]]
[compojure.core :refer [defroutes GET POST PUT DELETE]]
[compojure.route :as route]))
(defroutes handler'
(POST "/v1/todos" [] (res/response {:message "作成しました"}))
(GET "/v1/todos" [] (res/response {:message "Todoの一覧を返します"}))
(GET "/v1/todos/:id" [id] (res/response {:message (str "Todoを返します(id:" id ")")}))
(PUT "/v1/todos/:id" [id] (res/response {:message (str "更新しました(" id ")")}))
(DELETE "/v1/todos/:id" [id] (res/response {:message (str "削除しました(id: " id ")")}))
(route/not-found (res/response {:message "Not Found"})))
(def handler
(-> handler'
(wrap-json-response)))
(defn -main []
(jetty/run-jetty handler {:port 3000}))
これでjsonレスポンスが返せるようになりました
$ curl localhost:3000/v1/todos
{"message":"Todoの一覧を返します"}
それぞれのメソッドの処理部分を関数に切り出し、Todoっぽくしてみます
(ns todo.core
(:require [ring.adapter.jetty :as jetty]
[ring.util.response :as res]
[ring.middleware.json :refer [wrap-json-response]]
[compojure.core :refer [defroutes GET POST PUT DELETE]]
[compojure.route :as route]))
(defn- get-todo [id]
(res/response {:id 1 :title "朝食を食べる" :completed false}))
(defn- get-todos [req]
(res/response [{:id 1 :title "朝食を食べる" :completed false}
{:id 2 :title "昼食を食べる" :completed false}
{:id 3 :title "夕食を食べる" :completed false}]))
(defn- post-todos [req]
(res/response {:id 1 :title "朝食を食べる" :completed false}))
(defn- put-todo [id]
(res/response {:id 1 :title "朝食を食べる" :completed false}))
(defn- delete-todo [id]
(res/response {:id 1 :title "朝食を食べる" :completed false}))
(defroutes handler'
(POST "/v1/todos" req post-todos)
(GET "/v1/todos" [] get-todos)
(GET "/v1/todos/:id" [id] (get-todo id))
(PUT "/v1/todos/:id" [id] (put-todo id))
(DELETE "/v1/todos/:id" [id] (delete-todo id))
(route/not-found (res/response {:message "Not Found"})))
(defn- wrap-response [handler]
(fn [req]
(-> (handler req)
(res/response))))
(def handler
(-> handler'
(wrap-json-response)))
(defn -main []
(jetty/run-jetty handler {:port 3000}))
$ curl localhost:3000/v1/todos
[{"id":1,"title":"朝食を食べる","completed":false},{"id":2,"title":"昼食を食べる","completed":false},{"id":3,"title":"夕食を食べる","completed":false}]
wrap-json-body
次に、リクエストされるjsonも扱いやすくしていきます
wrap-json-body
というミドルウェアを使うとそれを実現できます
ただ res/response
の部分がとても冗長に感じるので、これもwrapするようなミドルウェアを作成します
(def handler
(-> handler'
(wrap-json-response)
(wrap-json-body {:keywords? true})))
このように ミドルウェアを足してみました
POST: /v1/todos
に対して
{
"title": "テストタイトル",
"completed": false
}
を送ると新しいTodoが作成されるようにしたいので、post-todo
の関数を修正し、受け取れるようにしてみます
送られたjsonは、request
のbody
にバインドされるため以下のようにして取り出すことにしました。
(defn- post-todos [req]
(let [{:keys [title completed]} (get-in req [:body])]
(res/response {:id 1 :title title :completed completed})))
実際にリクエストを送ってみます
$ curl localhost:3000/v1/todos -X POST -d '{"title": "テストタイトル", "completed": false}' -H 'Content-Type: application/json'
{"id":1,"title":"テストタイトル","completed":false}
これでjsonを扱うことができるようになりました