Skip to content

Instantly share code, notes, and snippets.

@usametov
Created March 28, 2025 05:35
Show Gist options
  • Save usametov/d06e2241f056135d678dd8d9ecfbcaed to your computer and use it in GitHub Desktop.
Save usametov/d06e2241f056135d678dd8d9ecfbcaed to your computer and use it in GitHub Desktop.
kit clj rest api example

The Kit framework (https://kit-clj.github.io/) is a lightweight, modular framework for scalable web development in Clojure. It uses Reitit for routing and Integrant for system management, making it well-suited for building REST APIs. Below is an example of how you can create a simple REST API using Kit, based on its conventions and structure.

This example assumes you’ve set up a Kit project (e.g., yourname/guestbook) and will demonstrate a basic REST API with endpoints to manage a list of messages (e.g., a guestbook). I'll walk you through the key parts.


Example: Simple Guestbook REST API

1. Project Setup

If you haven’t already, create a Kit project:

clojure -Tclj-new create :template io.github.kit-clj :name yourname/guestbook
cd guestbook

2. Add Dependencies

Ensure your deps.edn includes the necessary dependencies. Kit already includes Reitit and Ring, but you might want to add a database (e.g., SQLite via kit-sql). Run this in your project directory:

(kit/install-module :kit/sql)

This adds SQL support with SQLite by default. Restart your REPL after this.

3. Define the API Routes

Edit src/clj/yourname/guestbook/web/routes/api.clj (create it if it doesn’t exist). Here’s an example that defines RESTful endpoints:

(ns yourname.guestbook.web.routes.api
  (:require
   [reitit.ring :as ring]
   [ring.util.http-response :as response]
   [kit.edge.db.sql :refer [query]]))

;; Mock database operations (replace with real DB logic)
(defn get-messages [db]
  (query db {:select [:*] :from [:messages]}))

(defn add-message [db message]
  (query db {:insert-into :messages
             :values [{:name (:name message) :message (:message message)}]}))

;; Route handlers
(defn get-messages-handler [{:keys [db]}]
  (response/ok (get-messages db)))

(defn add-message-handler [{:keys [db body-params]}]
  (let [result (add-message db body-params)]
    (response/created "" {:message "Message added" :result result})))

;; API routes
(defn api-routes [{:keys [db]}]
  [["/messages"
    {:get {:summary "Get all messages"
           :handler (fn [req] (get-messages-handler (assoc req :db db)))}
     :post {:summary "Add a new message"
            :parameters {:body [:map
                               [:name string?]
                               [:message string?]]}
            :handler (fn [req] (add-message-handler (assoc req :db db)))}}]])
  • GET /api/messages: Retrieves all messages.
  • POST /api/messages: Adds a new message with a JSON body like {"name": "John", "message": "Hello!"}.

4. Configure the System

Edit resources/system.edn to include the API routes and database connection:

{
 :reitit.ring/router
 {:routes #ig/ref :reitit.routes/api
  :data {:middleware [;; Add middleware as needed
                      :ring.middleware.json/wrap-json-body
                      :ring.middleware.json/wrap-json-response]}}

 :reitit.routes/api
 {:base-path "/api"
  :db #ig/ref :db.sql/connection}

 :db.sql/connection
 #profile {:dev  {:jdbc-url "jdbc:sqlite:_dev.db"}
           :test {:jdbc-url "jdbc:sqlite:_test.db"}
           :prod {:jdbc-url #env JDBC_URL}}

 :db.sql/query-fn
 {:conn #ig/ref :db.sql/connection
  :options {}
  :filename "sql/queries.sql"}

 :db.sql/migrations
 {:store :database
  :db {:datasource #ig/ref :db.sql/connection}
  :migrate-on-init? true}
}

5. Define SQL Schema

Create resources/sql/queries.sql for the database schema and queries:

-- :name create-messages-table
-- :command :execute
CREATE TABLE IF NOT EXISTS messages (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  name TEXT NOT NULL,
  message TEXT NOT NULL,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)

-- :name get-messages :? :*
SELECT * FROM messages

-- :name add-message :! :n
INSERT INTO messages (name, message) VALUES (:name, :message)

6. Update Core Namespace

Edit src/clj/yourname/guestbook/core.clj to include the API routes:

(ns yourname.guestbook.core
  (:require
   [yourname.guestbook.web.routes.api]
   [kit.edge.db.sql]
   [integrant.core :as ig]))

;; Add :reitit.routes/api to the system config
(defmethod ig/init-key :reitit.routes/api [_ opts]
  (yourname.guestbook.web.routes.api/api-routes opts))

7. Run the Application

Start the development server:

clojure -M:run

The API will be available at http://localhost:3000/api.

8. Test the API

  • Get messages:

    curl http://localhost:3000/api/messages

    Response: [] (initially empty).

  • Add a message:

    curl -X POST http://localhost:3000/api/messages \
         -H "Content-Type: application/json" \
         -d '{"name": "John", "message": "Hello, Kit!"}'

    Response: {"message": "Message added", "result": 1}.

  • Get messages again:

    curl http://localhost:3000/api/messages

    Response: [{"id": 1, "name": "John", "message": "Hello, Kit!", "created_at": "2025-03-27T..."}].


Explanation

  • Routes: Defined using Reitit, which supports a data-driven approach to routing. The :parameters key ensures the POST request validates the JSON body.
  • Database: Uses kit-sql with SQLite for simplicity. You can swap it for PostgreSQL or another DB by adjusting the :db.sql/connection config.
  • Middleware: Added JSON handling middleware to parse request bodies and format responses.
  • System: Integrant ties everything together, injecting the database connection into the routes.

This is a basic example from the Kit framework’s structure. You can expand it by adding more endpoints, authentication, or error handling as needed. Check https://kit-clj.github.io/ for more details on customization!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment