Clojure and RESTful Web Services

Thomas van der Veen

@thomasvdv007

Acceptance Criteria

  • List all entries in the phone book
  • Create a new entry in the phone book
  • Remove an existing entry from the phone book
  • Update an existing entry in the phone book
  • Search for entries by surname
  • An entry has the following
    • Surname
    • First name
    • Address (optional)

CRUD

Create: POST

Read: GET

Update: PUT

Delete: DELETE

Validating input using Prismatic Schema

(def schema
  {:first-name s/Str
   :surname s/Str
   :phone-number s/Str
   (s/optional-key :address)
   {:place s/Str
    :country s/Str}})

Our main data-structure

(def phonebook
  (atom
   {:db
    {"80a8ea00-…"
     {:first-name "Thomas"
      :surname "van der Veen"
      :phone-number "0783312345"
      :address {:street "High Street"
                :postcode "SO21 1QQ"}}
     "38d77ce0-…"
     {:first-name "Malcolm"
      :surname "Sparks"
      :phone-number "07123456"}}

    :last-added "38d77ce0-…"}))

Compojure Routes

(defroutes app-routes
  (GET "/v1/phonebook" [] (get-phonebook))
  (POST "/v1/phonebook" {body :body}
        (add-user (slurp body)))
  (PUT "/v1/phonebook/:id"
       {body :body params :params}
       (update-user (:id params)
                    (slurp body)))
  (DELETE "/v1/phonebook/:id" [id]
          (delete-user id))
  (GET "/v1/phonebook/search"
       {params :params}
       (search-users params))
  (route/not-found "Not Found"))

Adding a new entry

(defn atom-user-add [db data]
  (let [new-uuid (.toString (UUID/randomUUID))
        new-db (assoc-in db [:db new-uuid] data)]
    (assoc-in new-db [:last-added] new-uuid)))

(defn add-user [data]
  (let [parsed-data (edn/read-string data)]
    (if-let [error (s/check schema parsed-data)]
      {:status 400
       :body (str "malformd request:\n" error)}
      (do
        (let [{id :last-added}
              (swap! phonebook-db atom-user-add parsed-data)]
          {:status 201 :body (pr-str id)})))))

Getting the entries

(defn get-phonebook []
  (let [data (pr-str (:db @phonebook-db))]
    (-> (r/response data)
        (r/content-type
         "application/edn"))))

Updating an entry

(defn update-user [id data]
  (let [parsed-data (edn/read-string data)]
    (if (contains? (:db @phonebook-db) id)
      (do
        (if-let [error (s/check schema parsed-data)]
          {:status 400
           :body (str "malformed request\n" error)}
          (do
            (swap! phonebook-db assoc-in [:db id]
                   parsed-data)
            {:status 200})))
      {:status 404 :body (str id " does not exist\n")})))

Deleting an entry

(defn delete-user [id]
  (if (contains? (:db @phonebook-db) id)
    (do (swap! phonebook-db update-in
               [:db] dissoc id)
        {:status 200})
    {:status 404 :body
     (str id " does not exist\n")}))

Searching for an entry

(defn search-users [params]
  (let [surname (:surname params)
        filtered (into {}
                       (filter
                        #(= surname (:surname (second %)))
                        (:db @phonebook-db)))]
    (-> (r/response (pr-str filtered))
        (r/content-type "application/edn"))))

References