0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【ひとりカレンダー】ClojureAdvent Calendar 2024

Day 15

Clojure: RingでTodoアプリを作る - jsonを扱う

Last updated at Posted at 2024-12-14

advent_calendar_2024.png

Advent Calendar 2024 Day 15

昨日は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は、requestbodyにバインドされるため以下のようにして取り出すことにしました。

(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を扱うことができるようになりました

0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?