clojure と reitit で簡単なAPIをつくってみる

以下のサイトなどを参考にさせてもらって。

シンプルなサーバー

まず、leininngenで、プロジェクトを作成

lein new user-api

project.cljを開いて、ring の依存を追加。

…
 :dependencies [[org.clojure/clojure "1.10.1"]
                [javax.servlet/servlet-api "2.5"] 
+                [ring "1.9.4"]]
…

とりあえず、ringのみで動かす。

引数にリクエストのマップを受けて、レスポンスのマップを返す関数(ハンドラー)を作成。
まずは、リクエストの内容に関係なく、bodyに"hello world"が入ったレスポンスを返す。

(ns user-api.core
  (:require [ring.adapter.jetty :as jetty]))

(defn handler [request]
  {:status 200
   :headers {"Content-Type" "text/html"}
   :body "Hello World"})

(defn start [] 
  (jetty/run-jetty handler {:port 3000
                            :join? false}))

replからサーバーを起動.

(def server (start))

ターミナルから、HTTPieで動作確認。

> http :3000
HTTP/1.1 200 OK
Content-Length: 11
Content-Type: text/html
Date: Tue, 14 Dec 2021 06:10:18 GMT
Server: Jetty(9.4.42.v20210604)

Hello World

サーバー停止

(.stop server)

ルーターを追加

ルーターライブラリはいろいろあって、チュートリアルでよく見かけるのは、Compojure,Bidi,Ataraxy(ductのとき)あたり?
今回はreititを使用。

project.cljを開いて、reitit を追加して、replを再起動

…
 :dependencies […
                [metosin/reitit "0.5.15"]
                [metosin/muuntaja "0.6.8"]]
…

reitit.ring/ring-router

 (ns user-api.core
   (:require [ring.adapter.jetty :as jetty]
+            [reitit.ring :as ring]
+            [muuntaja.core :as m]
+            [reitit.ring.middleware.muuntaja :as muuntaja])
   (:gen-class))

-(defn handler [_]
+(defn string-handler [_]

+ (def app
+  (ring/ring-handler
+   (ring/router
+    ["/"
+     ["" string-handler]]
+    {:data {:muuntaja m/instance
+            :middleware [muuntaja/format-middleware]}})))

(defn start [] 
  (jetty/run-jetty app {:port 3000       ;handler を app に変更
                        :join? false}))

ターミナルから、HTTPieで動作確認。

>http :3000
HTTP/1.1 200 OK
Content-Length: 5
Date: Fri, 17 Jun 2022 07:21:13 GMT
Server: Jetty(9.4.42.v20210604)

Hello

post で データを追加

  (defonce server (atom nil))
+ (def users (atom {}))
…
+  (defn create-user [{user :body-params}]
+    (let [id (str (java.util.UUID/randomUUID))
+          users (->> (assoc user :id id)
+                    (swap! users assoc id))]
+      {:status 201
+       :body (get users id)}))

…
  ["" string-handler]
+ ["users" {:post create-user}]]

ターミナルから、HTTPieで動作確認。

>http post :3000/users name=alice
HTTP/1.1 200 OK
Content-Length: 58
Content-Type: application/json;charset=utf-8
Date: Fri, 17 Jun 2022 07:40:45 GMT
Server: Jetty(9.4.42.v20210604)

{
    "id": "1244f409-f999-427b-97e0-08586e2078c4",
    "name": "alice"
}

getでデータの取得


+(defn get-users [_]
+  {:status 200
+   :body @users})

+(defn get-user-by-id [{{:keys [id]} :path-params}]
+  {:status 200
+   :body (get @users id)})

-     ["users" {:post create-user}]] 
+     ["users" {:get get-users
+               :post create-user}]
+     ["users/:id" get-user-by-id]]

ターミナルから、HTTPieで動作確認。

>http post :3000/users name=alice
…
>http post :3000/users name=bob
…
>http :3000/users/3f005a74-0f4c-4d25-90d0-ca332276247c
HTTP/1.1 200 OK
Content-Length: 60
Content-Type: application/json;charset=utf-8
Date: Fri, 17 Jun 2022 08:07:57 GMT
Server: Jetty(9.4.42.v20210604)
{
    "id": "3f005a74-0f4c-4d25-90d0-ca332276247c",
    "name": "alice"
}

> http :3000/users
HTTP/1.1 200 OK
Content-Length: 284
Content-Type: application/json;charset=utf-8
Date: Fri, 17 Jun 2022 08:08:57 GMT
Server: Jetty(9.4.42.v20210604)
{
    "3f005a74-0f4c-4d25-90d0-ca332276247c": {
        "id": "3f005a74-0f4c-4d25-90d0-ca332276247c",
        "name": "alice"
    },
    "b90cec39-bc8e-47d1-8f6c-f2996ef02a02": {
        "id": "b90cec39-bc8e-47d1-8f6c-f2996ef02a02",
        "name": "bob"
    }
}

put、delete


+(defn update-user [{{:keys [id]} :path-params user :body-params}]
+  (swap! users id user)
+  {:status 200
+   :body  "updated"})

+(defn delete-user [{{:keys [id]} :path-params}]
+  (swap! users dissoc id)
+  {:status 204
+   :body  ""}
)

…

-["users/:id" get-user-by-id]]
+["users/:id" {:get get-user-by-id
+              :put update-user
+              :delete delete-user}]

次はIntegrantを組み込んでみる。