<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Toni</title>
    <description>The latest articles on DEV Community by Toni (@tvaisanen).</description>
    <link>https://dev.to/tvaisanen</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1219287%2F63d1ad6a-adf8-4563-a13d-04af1895268d.jpg</url>
      <title>DEV Community: Toni</title>
      <link>https://dev.to/tvaisanen</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/tvaisanen"/>
    <language>en</language>
    <item>
      <title>Clojure for Node Developers: File System</title>
      <dc:creator>Toni</dc:creator>
      <pubDate>Thu, 23 Nov 2023 06:10:11 +0000</pubDate>
      <link>https://dev.to/tvaisanen/clojure-for-node-developers-file-system-hb9</link>
      <guid>https://dev.to/tvaisanen/clojure-for-node-developers-file-system-hb9</guid>
      <description>&lt;p&gt;This will be a straightforward post with Javascript to Clojure (on &lt;strong&gt;JVM&lt;/strong&gt; ) examples of filesystem operations: listing, reading, writing, renaming, and deleting files. Let me know if you're interested in how to use Node packages from Clojurescript.&lt;/p&gt;

&lt;p&gt;So, let's get to it.&lt;/p&gt;

&lt;p&gt;In Javascript, we'd import the &lt;code&gt;fs&lt;/code&gt;-module.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const fs = require('node:fs/promises')

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;I'll be running all of the Javascript blocks inside a &lt;code&gt;(async () =&amp;gt; { ...code... })()&lt;/code&gt; closure.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In Clojure, we'd reach out for &lt;code&gt;clojure.java.io&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(require '[clojure.java.io :as io])

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Node Filehandle and Java File
&lt;/h2&gt;

&lt;p&gt;In Javascript, we can get a reference to a file with a &lt;code&gt;Filehandle&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;let directory = await fs.open(".");

console.log(directory)

// FileHandle {
// close: [Function: close],
// [Symbol(kHandle)]: FileHandle {},
// [Symbol(kFd)]: 20,
// [Symbol(kRefs)]: 1,
// [Symbol(kClosePromise)]: null
// }

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;FileHandle&lt;/code&gt; can be either a directory or a file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;let directoryStats = await fs.stat(".");
directoryStats.isFile(); // false
directoryStats.isDirectory(); // true

let fileStats = await fs.stat("files/a.txt");
fileStats.isFile()); // true
fileStats.isDirectory(); // false

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The basic building block in Clojure is &lt;code&gt;java.io.File&lt;/code&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;User interfaces and operating systems use system-dependent &lt;em&gt;pathname strings&lt;/em&gt; to name files and directories. This class presents an abstract, system-independent view of hierarchical pathnames.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/io/File.html"&gt;Docs: java.io.File&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(io/file ".")
;; =&amp;gt; #object[java.io.File 0x54162701 "."]

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Similarly to &lt;code&gt;Filehandle&lt;/code&gt; in JS, &lt;code&gt;java.io.File&lt;/code&gt; can be either a directory or a file. The &lt;code&gt;File&lt;/code&gt; has &lt;code&gt;isDirectory&lt;/code&gt; and &lt;code&gt;isFile&lt;/code&gt; methods that can be called via Java-interop.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(.isDirectory (io/file "."))
;; =&amp;gt; true

(.isFile (io/file "."))
;; =&amp;gt; false

(.isDirectory (io/file "deps.edn"))
;; =&amp;gt; false

(.isFile (io/file "deps.edn"))
;; =&amp;gt; true

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's see what other properties the &lt;code&gt;java.io.File&lt;/code&gt; has with the &lt;a href="https://clojuredocs.org/clojure.core/bean"&gt;&lt;code&gt;bean&lt;/code&gt;&lt;/a&gt; function.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;bean&lt;/p&gt;

&lt;p&gt;Takes a Java object and returns a read-only implementation of the map abstraction based upon its JavaBean properties.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/clojure/clojure/blob/clojure-1.11.1/src/clj/clojure/core_proxy.clj#L403"&gt;Source&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(bean (io/file "."))
;; =&amp;gt; 
{:path ".",
 :freeSpace 146245873664,
 :parent nil,
 :directory true,
 :parentFile nil,
 :name ".",
 :file false,
 :canonicalFile
 #object[java.io.File 0x4f2abe7e "/home/tvaisanen/projects/tmp"],
 :absolute false,
 :absoluteFile
 #object[java.io.File 0x5594224b "/home/tvaisanen/projects/tmp/."],
 :hidden true,
 :class java.io.File,
 :canonicalPath "/home/tvaisanen/projects/tmp",
 :usableSpace 124331769856,
 :totalSpace 429923737600,
 :absolutePath "/home/tvaisanen/projects/tmp/."}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Listing Files
&lt;/h2&gt;

&lt;p&gt;Given we have the following file structure.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; tree files
files
 a.txt
 b.txt
 c.txt
 nest
     a.txt

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In Javascript, we'd typically write.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;let dir = await fs.readdir("files");
console.log(dir);
// ['a.txt', 'b.txt', 'c.txt', 'nest']

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And in Clojure, we'll take the folder we want to list as &lt;code&gt;java.io.File&lt;/code&gt; and list all the files with &lt;a href="https://github.com/clojure/clojure/blob/clojure-1.11.1/src/clj/clojure/core.clj#L4973"&gt;&lt;code&gt;file-seq&lt;/code&gt;&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(file-seq (io/file "files"))
;; =&amp;gt;
(#object[java.io.File 0x7e2b40d7 "files"]
 #object[java.io.File 0x22aaad8f "files/a.txt"]
 #object[java.io.File 0x3730fe92 "files/b.txt"]
 #object[java.io.File 0x62aaf1b "files/c.txt"]
 #object[java.io.File 0x22236af1 "files/nest"]
 #object[java.io.File 0x55126def "files/nest/a.txt"])

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I'd expect this to return files similarly &lt;code&gt;fs.readdir&lt;/code&gt;, but now we have the nested files in the listing. Let's see why that happens and if we could get it to work similarly to the listing in Javascript.&lt;/p&gt;

&lt;p&gt;If we take a look at the &lt;code&gt;file-seq&lt;/code&gt; &lt;a href="https://github.com/clojure/clojure/blob/clojure-1.11.1/src/clj/clojure/core.clj#L4973"&gt;source code&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(defn file-seq
  "A tree seq on java.io.Files"
  {:added "1.0"
   :static true}
  [dir]
    (tree-seq
     (fn [^java.io.File f] (. f (isDirectory)))
     (fn [^java.io.File d] (seq (. d (listFiles))))
     dir))

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The docstring says that the function returns a tree seq, which sounds like a non-flat structure. And if we look deeper into &lt;code&gt;tree-seq&lt;/code&gt; function and get the docstring.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Returns a lazy sequence of the nodes in a tree, via a depth-first walk. ...&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And here's the explanation of what is happening: "a depth-first walk." This function will go first till the end of the file tree and start coming back towards the root directory. The first argument is a function that returns a boolean value to decide whether the node (read file or directory) needs to be traversed (read looked into). If it returns true, the same file is passed to the second function to list its nodes (read files and subdirectories). Based on this, the tree-seq will do a depth-first search and, therefore, read all the subdirectories.&lt;/p&gt;

&lt;p&gt;You might have already noticed that the second function argument calls &lt;code&gt;(. d (listFiles))&lt;/code&gt; to get the files for our directory &lt;code&gt;d&lt;/code&gt;. Let's use this to mimic the JS version &lt;code&gt;readdir&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(seq (. (io/file "files") listFiles))
;; =&amp;gt;
(#object[java.io.File 0x356bf4d3 "files/a.txt"]
 #object[java.io.File 0x13c28931 "files/b.txt"]
 #object[java.io.File 0x444349ab "files/c.txt"]
 #object[java.io.File 0x2e64f09c "files/nest"])

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this point, the expected result is almost identical to the Javascript's &lt;code&gt;fs.readdir&lt;/code&gt;. We have the files listed in the directory, but instead of file names, we have &lt;code&gt;java.io.File&lt;/code&gt; instances. Let's iterate over the files and once again use Java-interop with the &lt;code&gt;getName&lt;/code&gt; method.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(for [file (seq (. (io/file "files") listFiles))]
  (.getName file))
;; =&amp;gt; ("a.txt" "b.txt" "c.txt" "nest")

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;fs.readdir&lt;/code&gt; function will throw if called with a filename that is not a directory. If we try to create a file sequence from a &lt;code&gt;java.io.File&lt;/code&gt; that is not a directory, we get only the file itself in a list.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(file-seq (io/file "files/a.txt"))
;; =&amp;gt; (#object[java.io.File 0x37eeb5e7 "files/a.txt"])

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's combine what we've learned from the previous examples into a new function &lt;code&gt;read-dir&lt;/code&gt; that takes a path, checks that it is not a file, and then lists the files and directories in the folder but not the subpages.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(defn read-dir [d]
  (let [directory (io/file d)]
    (when (. directory isFile)
      (throw (ex-info "Path is not a directory" {:path directory})))
    (doall
     (for [file (seq (. directory listFiles))]
       (.getName file)))))

(read-dir "files")
;; =&amp;gt; ("a.txt" "b.txt" "c.txt" "nest")

(read-dir "files/a.txt")
;; Unhandled clojure.lang.ExceptionInfo
;; Path is not a directory
;; {:path #object[java.io.File 0x7c7a2da9 "files/a.txt"]}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's enough on listing files for now. Next, let's look into reading the files.&lt;/p&gt;

&lt;h2&gt;
  
  
  Read Files
&lt;/h2&gt;

&lt;p&gt;Let's see the content for a couple of the example files.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; cat files/a.txt
AAAAA
 cat files/b.txt
BBBB
1111
2222
3333

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In Javascript, we'd typically use the &lt;code&gt;fs.readFile&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;let content = await fs.readFile("files/a.txt", {encoding: "utf8"});
console.log(content);
// AAAAA

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In Clojure, we default to &lt;code&gt;slurp&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(slurp (io/file "files/a.txt"))
;; =&amp;gt; "AAAAA\n"

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Slurp also works with the filename since if the argument is a string; it's first coerced (read interpreted as) as a URI and second as a local file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(slurp "files/a.txt")
;; =&amp;gt; "AAAAA\n"

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both &lt;code&gt;fs.readdir&lt;/code&gt; and &lt;code&gt;slurp&lt;/code&gt; read the file into memory at once; therefore, you might want to avoid it in production!&lt;/p&gt;

&lt;p&gt;The more responsible way is to read the files as streams line by line.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;let file = await fs.open('files/b.txt',);

for await (const line of file.readLines()) {
    console.log(line);
}
// BBBB
// 1111
// 2222
// 3333

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In Clojure, we can use the &lt;code&gt;java.io.Reader&lt;/code&gt; to create a buffered reader for streaming over the files. &lt;code&gt;slurp&lt;/code&gt; uses the same mechanism, but instead of reading a file line by line, it reads it all simultaneously.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(io/reader "files/a.txt")
;; =&amp;gt; #object[java.io.BufferedReader 0x4754cc18 "java.io.BufferedReader@4754cc18"]

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can open this stream (read file) to create a lazy line sequence that can be processed one line at a time.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(with-open [reader (io/reader "files/b.txt")]
  (doall (for [line (line-seq reader)]
           (str "read: " line))))
;; =&amp;gt; ("read: BBBB" "read: 1111" "read: 2222" "read: 3333")

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, to writing files.&lt;/p&gt;

&lt;h2&gt;
  
  
  Writing Files
&lt;/h2&gt;

&lt;p&gt;With Javascript, we'd typically do this with &lt;code&gt;fs.writeFile&lt;/code&gt; or use &lt;code&gt;fs.createFileStream&lt;/code&gt; for extra control.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;await fs.writeFile("files/write-with-js.txt", "writing from JS");

let content_01 = await fs.readFile("files/write-with-js.txt", 
                                   {encoding: "utf8"});
console.log(content); 
// writing from JS

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In Clojure, similarly to &lt;code&gt;slurp&lt;/code&gt; we have &lt;code&gt;spit&lt;/code&gt; to write data.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(spit "files/new.txt" "New content here")
;; =&amp;gt; nil
(slurp "files/new.txt")
;; =&amp;gt; "New content here"

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By default, &lt;code&gt;spit&lt;/code&gt; overrides the file with the given content. To add to the file, use arguments &lt;code&gt;:append true&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(spit "files/new.txt" "Append to file")
;; =&amp;gt; nil
(slurp "files/new.txt")
;; =&amp;gt; "Append to file"
(spit "files/new.txt" " Try again" :append true)
;; =&amp;gt; nil
(slurp "files/new.txt")
;; =&amp;gt; "Append to file Try again"

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;spit&lt;/code&gt; wraps the &lt;code&gt;java.io.writer&lt;/code&gt;, which can be used directly. Similarly to &lt;code&gt;slurp&lt;/code&gt; &lt;code&gt;java.io.writer&lt;/code&gt; replaces the file content if &lt;code&gt;:append true&lt;/code&gt; is not passed as an option.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(with-open [writer (io/writer "files/other.txt")]
  (.write writer "text here"))

(slurp "files/other.txt")
;; =&amp;gt; "text here"

(with-open [writer (io/writer "files/other.txt" :append true)]
  (.write writer " more text"))
;; =&amp;gt; nil

(slurp "files/other.txt")
;; =&amp;gt; "text here more text"

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's go through a couple of more use cases.&lt;/p&gt;

&lt;h2&gt;
  
  
  Renaming Files
&lt;/h2&gt;

&lt;p&gt;In Javascript, we rename files with &lt;code&gt;fs.rename&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;await fs.rename("files/c.txt", "files/d.txt");

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In Clojure, we use the &lt;code&gt;renameTo&lt;/code&gt; method.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(.renameTo (io/file "files/new.txt")
           (io/file "files/newer.txt"))
;; =&amp;gt; true
(.isFile (io/file "files/new.txt"))
;; =&amp;gt; false
(.isFile (io/file "files/newer.txt"))
;; =&amp;gt; true

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Deleting Files
&lt;/h2&gt;

&lt;p&gt;In Javascript, we delete files with &lt;code&gt;fs.unlink&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; await fs.unlink("files/d.txt");

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In Clojure, we have &lt;code&gt;clojure.java.io/delete-file&lt;/code&gt; for the same task.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(.isFile (io/file "files/other.txt"))
;; =&amp;gt; true
(io/delete-file "files/other.txt")
;; =&amp;gt; true
(.isFile (io/file "files/other.txt"))
;; =&amp;gt; false

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Clojure can do the same tasks as Javascript on the filesystem level since &lt;code&gt;clojure.java.io&lt;/code&gt; provides utility functions as a layer on top of &lt;code&gt;java.io&lt;/code&gt; classes. If there's no function matching your need, you can use Java-interop by, for example, using the &lt;code&gt;java.io.File&lt;/code&gt; methods. If you hit a roadblock, check the following resources for more hints.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://clojuredocs.org/clojure.java.io"&gt;ClojureDocs: clojure.java.io&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/io/File.html"&gt;JavaDocs: java.io.File&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once again, I hope you found this helpful. Feel free to reach out and let me know what pain points you might have encountered when learning Clojure and what type of posts would help make the jumpsocial links in the menu.&lt;/p&gt;

&lt;p&gt;Thank you for reading.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Take Your Linting Game to the Next Level!</title>
      <dc:creator>Toni</dc:creator>
      <pubDate>Tue, 14 Nov 2023 18:25:22 +0000</pubDate>
      <link>https://dev.to/tvaisanen/take-your-linting-game-to-the-next-level-2fom</link>
      <guid>https://dev.to/tvaisanen/take-your-linting-game-to-the-next-level-2fom</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;TLDR: It is possible to declare types for Clojure functions and make them available to the library users by creating clj-kondo configs (type exports). The configs can be created with Malli by collecting function schemas and then transforming these into clj-kondo types in a config file. If the config files are committed into resources, they can be imported by the host project when used as a dependency. Having Malli schemas has the added benefit of enabling the run-time checks via Malli instrumentation.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I like imagining an ecosystem where there'd be a clj-kondo config available for the most used libraries. I thought I would chime in my two cents on spreading the know-how to spread the word on how to do this. I'm by no means an expert on the topic, but I've learned a thing or two that might also be helpful to others.&lt;/p&gt;

&lt;p&gt;Suppose you are unfamiliar with Malli and clj-kondo and how they can help you with static code analysis. In that case, I recommend first reading &lt;a href="https://blog.tvaisanen.com/data-validation-in-clojure"&gt;Data Validation in Clojure&lt;/a&gt; and &lt;a href="https://blog.tvaisanen.com/typescript-like-intellisense-for-clojure-functions-with-malli"&gt;Typescript Like Intellisense for Clojure Functions With Malli&lt;/a&gt; for background.&lt;/p&gt;

&lt;p&gt;This time, I want to dig deeper into how these tools can improve the developer experience by making the types available to our code's human and CI consumers. I don't know if this is already yesterday's news, but this trick is worth knowing.&lt;/p&gt;

&lt;p&gt;Let's explore the topic by creating a library, publishing it to Github, then using it as a dependency for another project, and making the library types available to benefit from the extended clj-kondo features.&lt;/p&gt;

&lt;h2&gt;
  
  
  Publish Library With Types
&lt;/h2&gt;

&lt;p&gt;The only required dependency is Malli, which will be used to generate the clj-kondo types.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note that in a "real" project Malli would be added as an extra dependency for an alias when creating the types if the library itself is not depending on Malli. If this is something that doesn't make sense to you let me know in the comments.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{:paths ["src"]
 :deps {org.clojure/clojure {:mvn/version "1.11.1"}
        metosin/malli {:mvn/version "0.13.0"}}}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;My library will have two functions: adding (x,y) coordinate points and rendering points as a string. If you need help defining the schemas for your use case, check out &lt;a href="https://cljdoc.org/d/metosin/malli/0.13.0/doc/function-schemas"&gt;Malli function schema docs&lt;/a&gt; for reference.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(ns tvaisanen.types-export-sample.core
  "Example on how to create and export type clj-kondo
  type definitions that can be installed in a similar way
  that @types/lib-name is used in Typescript.")

(def Point [:map
            [:x :int]
            [:y :int]])

(defn add-points
  {:malli/schema [:=&amp;gt; [:cat Point Point] Point]}
  [p1 p2]
  (merge-with + p1 p2))

(defn render-point
  {:malli/schema [:=&amp;gt; [:cat Point] :string]}
  [p]
  (str p))

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The only difference from what you'd typically write is the added metadata key pair. This will be enough for us to get the benefits in later stages when we want to provide the type hints for the library users. Let's take a look at how to make that happen next.&lt;/p&gt;

&lt;h3&gt;
  
  
  Export Configuration
&lt;/h3&gt;

&lt;p&gt;Exporting is the process of creating the library's clj-kondo configuration under the resources folded as the &lt;a href="https://cljdoc.org/d/clj-kondo/clj-kondo/2023.10.20/doc/configuration#exporting"&gt;documentation&lt;/a&gt; instructs. To do this, I created a naive exporting function that:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Also collects the &lt;code&gt;malli/schema&lt;/code&gt; schemas from the functions,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;generates the type definitions under the Malli &lt;code&gt;clj-kondo-types&lt;/code&gt;,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;copies the &lt;code&gt;config.edn&lt;/code&gt; files into resources,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;and cleans the &lt;code&gt;clj-kondo-types&lt;/code&gt; cache.&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(require '[malli.dev :as dev]
         '[clojure.java.io. :as io])

(defn export-types []
  ;; collect schemas and start instrumentation
  (dev/start!)

  ;; create export file
  (def export-file
    (io/file "resources/clj-kondo/clj-kondo.exports/tvaisanen/export-types-sample/config.edn"))

  ;; make parents if not exist
  (io/make-parents export-file)

  ;; copy the configs
  (io/copy
   (io/file ".clj-kondo/metosin/malli-types-clj/config.edn")
   export-file)

  ;; clear the cache and stop instrumentation
  (dev/stop!))

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If we inspect the &lt;code&gt;.clj-kondo&lt;/code&gt; cache folder, we can see that there's a config for &lt;code&gt;malli-types-clj&lt;/code&gt; which is used to store the temporary cache while the Malli instrumentation is active &lt;code&gt;(dev/start!)&lt;/code&gt; and it's cleared on &lt;code&gt;(dev/stop!)&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; tree .clj-kondo
.clj-kondo
 metosin
     malli-types-clj
         config.edn

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What we just did with the export function was copy this &lt;code&gt;config.edn&lt;/code&gt; file from the clj-kondo cache into our libs resources for persistence.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; tree resources
resources
 clj-kondo
     clj-kondo.exports
         tvaisanen
             export-types-sample
                 config.edn

5 directories, 1 file

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the folder structure that clj-kondo expects to see when it's looking for available configurations to be imported. In my case the &lt;code&gt;config.edn&lt;/code&gt; looks like this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{:linters
 {:unresolved-symbol {:exclude [(malli.core/=&amp;gt;)]},
  :type-mismatch
  {:namespaces
   {export-types-sample.core

    {render-point
     {:arities
      {1 {:args [{:op :keys,
                  :req {:x :int,
                        :y :int}}],
          :ret :string}}},

     add-points
     {:arities
      {2 {:args [{:op :keys,
                  :req {:x :int,
                        :y :int}}
                 {:op :keys,
                  :req {:x :int,
                        :y :int}}],
          :ret {:op :keys,
                 :req {:x :int, :y :int}}}}}}}}}}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From here, we can see that argument and return types are defined for two functions &lt;code&gt;render-point&lt;/code&gt; and &lt;code&gt;add-points&lt;/code&gt; that have. This tells clj-kondo how the function arguments and return value should look when it's doing the static code analysis.&lt;/p&gt;

&lt;p&gt;We are almost done with the first step: creating our library with typehints. Next, commit and push the changes to your Git repo. &lt;a href="https://github.com/tvaisanen/export-types-sample"&gt;Here's mine&lt;/a&gt; for a reference.&lt;/p&gt;

&lt;h2&gt;
  
  
  Load the Published Library as a Dependency
&lt;/h2&gt;

&lt;p&gt;Now that we have "published" a library with clj-kondo configs, we can use this as a dependency for another project.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{:paths ["src"]
 :deps {org.clojure/clojure {:mvn/version "1.11.1"}

        metosin/malli {:mvn/version "0.13.0"}

        tvaisanen/export-types-sample
        {:git/sha "29f9b259975bde304300e4ca69c70d61622cabab"
         :git/url "https://github.com/tvaisanen/export-types-sample.git"}}}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;If you didn't use Github see the available gitlib config options from the &lt;a href="https://clojure.org/reference/deps_and_cli#_coord_attributes"&gt;docs&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;When we fetch the source code for the dependencies, we receive the resources (read configs) in our local cache and the malli function schemas within the source code. We don't need to install additional libs or types to access them. Next, we must collect these configs to our project's &lt;code&gt;.clj-kondo&lt;/code&gt; cache. &lt;a href="https://cljdoc.org/d/clj-kondo/clj-kondo/2023.10.20/doc/configuration#importing"&gt;Clj-kondo docs&lt;/a&gt; provide the steps on how to do this. Remember to create the &lt;code&gt;.clj-kondo&lt;/code&gt; folder if it doesn't exist before copying the configs.&lt;/p&gt;

&lt;p&gt;First, we copy the configs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; clj-kondo --lint "$(clojure -Spath)" --copy-configs --skip-lint

Imported config to .clj-kondo/tvaisanen/export-types-sample. 
To activate, add "tvaisanen/export-types-sample" to 
:config-paths in .clj-kondo/config.edn.

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then update the &lt;code&gt;.clj-kondo/config.edn&lt;/code&gt; (you might need to create the file) as instructed by clj-kondo output.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{:config-paths ["tvaisanen/export-types-sample"]}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And I am then populating the cache with.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ clj-kondo --lint $(clojure -Spath) --dependencies --parallel

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After this, we should be good to jump into the editor and see if everything worked as expected.&lt;/p&gt;

&lt;h3&gt;
  
  
  Benefits of Types in the Editor
&lt;/h3&gt;

&lt;p&gt;I created a &lt;code&gt;core&lt;/code&gt; namespace for the new project where I've required the library I set up. You can see that when &lt;code&gt;add-points&lt;/code&gt; is called with an invalid &lt;code&gt;sample/Point&lt;/code&gt; it tells us that we are missing a required key &lt;code&gt;:y&lt;/code&gt;. Without the extra type config, we should have an issue with "duplicate key :x", so it looks like the lib configs trump the default linting issues in priority.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--5EFVsf_2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn.hashnode.com/res/hashnode/image/upload/v1699694923692/377935a6-3770-4122-9f74-9a06a471bfcb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--5EFVsf_2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn.hashnode.com/res/hashnode/image/upload/v1699694923692/377935a6-3770-4122-9f74-9a06a471bfcb.png" alt="" width="800" height="395"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As we extend the code, we can see that &lt;code&gt;add-points&lt;/code&gt; return a value that the &lt;code&gt;render-point&lt;/code&gt; is okay with according to clj-kondo, but if we pass a map that is not a &lt;code&gt;sample/Point&lt;/code&gt; we get another linting error on the screen.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--K4vD0jV2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn.hashnode.com/res/hashnode/image/upload/v1699695090293/ad1b0e9b-6d63-4782-a361-042a89cbc512.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--K4vD0jV2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn.hashnode.com/res/hashnode/image/upload/v1699695090293/ad1b0e9b-6d63-4782-a361-042a89cbc512.png" alt="" width="800" height="453"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;These red squiggly lines can save you a lot of headaches by letting you know where the types don't match the expected value. But this is just the first benefit that comes from using the types. So far, we haven't looked into how Malli expands on this.&lt;/p&gt;

&lt;h3&gt;
  
  
  Benefits of Types in the REPL
&lt;/h3&gt;

&lt;p&gt;If a library has Malli schema function definitions, we can start instrumentation (monitoring with what values the functions are called and what they return) and get run-time type checking. In practice, this means we get a report on "invalid function call" in the REPL every time a function is called with unexpected arguments.&lt;/p&gt;

&lt;p&gt;Let me demonstrate this.&lt;/p&gt;

&lt;p&gt;We can use this to our advantage since we created the library with the malli schemas instead of writing the type hints manually into the clj-kondo config. Let's require &lt;code&gt;malli.dev&lt;/code&gt; in our namespace and run &lt;code&gt;dev/start!&lt;/code&gt; as we did when we first exported the types. Now, try running the &lt;code&gt;sample/render-point&lt;/code&gt; with an invalid point argument.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--uzPHiFAh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn.hashnode.com/res/hashnode/image/upload/v1699696127891/c9b38664-c10c-4d03-b52c-71bf5c6f1ceb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--uzPHiFAh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn.hashnode.com/res/hashnode/image/upload/v1699696127891/c9b38664-c10c-4d03-b52c-71bf5c6f1ceb.png" alt="" width="800" height="187"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When we evaluate line 19 while the malli instrumentation is active, we can find something new in the REPL, a schema error nicely documenting what is happening. The REPL provides helpful information about what went wrong with the function call.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--avG0pYMT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn.hashnode.com/res/hashnode/image/upload/v1699696129542/1365c14a-43fb-479f-990b-5b0b811aca00.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--avG0pYMT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn.hashnode.com/res/hashnode/image/upload/v1699696129542/1365c14a-43fb-479f-990b-5b0b811aca00.png" alt="" width="800" height="857"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Of course, we must remember that if there are a lot of functions being called, having the instrumentation on all the time can have a performance penalty.&lt;/p&gt;

&lt;p&gt;Just keep in mind when running &lt;code&gt;dev/start!&lt;/code&gt; the types are created into &lt;code&gt;.clj-kondo/metosin/malli-types-clj/config.edn&lt;/code&gt; and when the &lt;code&gt;dev/stop!&lt;/code&gt; is run. These types will be cleaned up.&lt;/p&gt;

&lt;h3&gt;
  
  
  Benefit of Types in the Terminal / CI
&lt;/h3&gt;

&lt;p&gt;Last but least, we can also use the generated types outside our editor by using &lt;code&gt;clj-kondo&lt;/code&gt; from the command line.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; clj-kondo --lint "src"
src/core.clj:6:2: error: Missing required key: :y
src/core.clj:6:8: error: duplicate key :x
src/core.clj:13:22: error: Missing required key: :x
src/core.clj:13:22: error: Missing required key: :y
linting took 5ms, errors: 4, warnings: 0

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Adding this step can help keep many unnecessary bugs out of production when added to the continuous integration checks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where to Contribute
&lt;/h2&gt;

&lt;p&gt;If you find that your particular use case is not supported for some reason. Providing patches is encouraged. Malli defines the &lt;code&gt;malli-&amp;gt;clj-kondo&lt;/code&gt; transformations in &lt;a href="https://github.com/metosin/malli/blob/master/src/malli/clj_kondo.cljc"&gt;&lt;code&gt;malli.clj-kondo&lt;/code&gt;&lt;/a&gt; namespace, which is where you want to look if you want to learn how the transformation process works or if you're going to extend the functionality. On the clj-kondo side, read the &lt;a href="https://github.com/clj-kondo/clj-kondo/blob/master/doc/types.md"&gt;status of types&lt;/a&gt; and this &lt;a href="https://github.com/clj-kondo/clj-kondo/blob/d9fca2705863e3e604e004ccb942e0b3d2e268ec/src/clj_kondo/impl/types.clj#L18-L51"&gt;source code file&lt;/a&gt; listing the available type mappings.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;That was probably a lot to digest in one go. It took me some time to understand the relationship between clj-kondo and Malli, but slowly and surely, it's making more sense. I wanted to write about this topic because I think if more people knew how to add the types to their projects, it would improve the developer experience across the whole ecosystem (at least, this is what I'd like to imagine).&lt;/p&gt;

&lt;p&gt;This is somewhat what happened with Javascript and Typescript. Little by little, the majority of the libs started offering type definitions. I don't know about you, but I'd appreciate a little extra help from my editor in debugging the expected types for a given function and having the change occasionally to turn on the Malli instrumentation to provide some run-time info on what's going wrong.&lt;/p&gt;

&lt;p&gt;Some tools can provide the observability to run-time values like &lt;a href="https://www.flow-storm.org/"&gt;Flowstrom&lt;/a&gt; or just good old &lt;a href="https://github.com/clojure/tools.trace"&gt;clojure/tools.trace&lt;/a&gt;. However, these tools do not tell me what type of data the original author had intended to receive.&lt;/p&gt;

&lt;p&gt;Once again, I hope you found this helpful. Feel free to reach out and let me know what you thinksocial links in the menu.&lt;/p&gt;

&lt;p&gt;Thank you for reading.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Deploying Clojure Like a Seasoned Hobbyist</title>
      <dc:creator>Toni</dc:creator>
      <pubDate>Tue, 07 Nov 2023 18:05:12 +0000</pubDate>
      <link>https://dev.to/tvaisanen/deploying-clojure-like-a-seasoned-hobbyist-13gd</link>
      <guid>https://dev.to/tvaisanen/deploying-clojure-like-a-seasoned-hobbyist-13gd</guid>
      <description>&lt;p&gt;This is a tutorial on how to set up a minimal Clojure app development environment with Postgres on DigitalOcean using Terraform. Although there will be room for further optimization and improvement in the implementation, this post focuses on providing the most straightforward template for setting up a project.&lt;/p&gt;

&lt;p&gt;I prefer configuring deployments in declarative Terraform files over clicking through dashboards. This helps me to get back on track when I return to my abandoned side projects.&lt;/p&gt;

&lt;p&gt;It's worth mentioning that I will be using a local terraform state, although I don't recommend it. Lately, I have been using Terraform Cloud for my projects. If you want to learn how to set up a Github CI with Terraform Cloud for this project, please let me know in the comments.&lt;/p&gt;

&lt;p&gt;Most platforms like AWS, GCP, Azure, Linode, Fly, and Digitalocean support Terraform. The big three, AWS, GCP, and Azure, feel excessive for hobby projects. Additionally, managing virtual servers or utilizing Kubernetes is not something I want to deal with, so that crosses Linode off the list, leaving me with Fly and Digitalocean as the options. While I haven't tested Fly yet, DigitalOcean's app platform has become my go-to choice out of habit and because the development experience feels suitable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Requirements
&lt;/h2&gt;

&lt;p&gt;We need &lt;a href="https://developer.hashicorp.com/terraform/downloads"&gt;Terraform&lt;/a&gt;, &lt;a href="https://docs.digitalocean.com/reference/doctl/how-to/install/"&gt;Doctl&lt;/a&gt;, &lt;a href="https://docs.docker.com/engine/install/"&gt;Docker&lt;/a&gt;, and &lt;a href="https://docs.docker.com/compose/install/"&gt;Docker Compose&lt;/a&gt; installed on our development machine and a &lt;a href="https://www.digitalocean.com/"&gt;DigitalOcean&lt;/a&gt; account with an &lt;a href="https://docs.digitalocean.com/reference/api/create-personal-access-token/"&gt;API token&lt;/a&gt; with write permissions that we can use to authenticate ourselves from the command line.&lt;/p&gt;

&lt;p&gt;You should be able to follow up on the examples without prior knowledge of Terraform. Still, if you're interested in learning more, the &lt;a href="https://developer.hashicorp.com/terraform/intro"&gt;official website&lt;/a&gt; is an excellent place to start.&lt;/p&gt;

&lt;h2&gt;
  
  
  Application
&lt;/h2&gt;

&lt;p&gt;To get started, create the following folder structure for the project. Clojure-related code and required Docker files are in the &lt;code&gt;api&lt;/code&gt; folder, where &lt;code&gt;docker-compose.yml&lt;/code&gt; is for running the application locally with a Postgres database, and the DigitalOcean-related Terraform code will be in the &lt;code&gt;terraform&lt;/code&gt; folder.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;project-root
 api
    deps.edn
    docker-compose.yml
    Dockerfile
    src
        main.clj
 terraform
     main.tf
     output.tf
     setup.tf

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Clojure
&lt;/h3&gt;

&lt;p&gt;First, let's start by defining the Clojure dependencies for the application in the &lt;code&gt;api/deps.edn&lt;/code&gt;. We need &lt;code&gt;ring&lt;/code&gt; dependencies for serving the API and &lt;code&gt;next.jdbc&lt;/code&gt; and &lt;code&gt;postgresql&lt;/code&gt; for the database access.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{:paths ["src"]

 :deps {org.clojure/clojure {:mvn/version "1.11.0"}
        ring/ring-core {:mvn/version "1.6.3"}
        ring/ring-jetty-adapter {:mvn/version "1.6.3"}
        com.github.seancorfield/next.jdbc {:mvn/version "1.2.659"}
        org.postgresql/postgresql {:mvn/version "42.2.10"}}

 :aliases {:run {:main-opts ["-m" "main"]
                 :exec-fn main/run!}}}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, create the code to serve the API, connect to the database, and make a database query in &lt;code&gt;api/src/main.clj&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(ns main
  (:require [ring.adapter.jetty :as jetty]
            [next.jdbc :as jdbc])
  (:gen-class))

(def port (Integer/parseInt (System/getenv "PORT")))

(def db {:dbtype "postgres"
         :jdbcUrl (System/getenv "JDBC_DATABASE_URL")})

(def ds (jdbc/get-datasource db))

(defn app [_request]
  (let [db-version (jdbc/execute! ds ["SELECT version()"])]
    {:status 200
     :headers {"Content-Type" "application/edn"}
     :body (str db-version)}))

(defn run! [&amp;amp; _args]
  (jetty/run-jetty #'app {:port port}))

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is all of the application code. The next step is to create a Dockerfile and set up the local development environment to validate that the database connection and the API are working as expected when provided with the expected environment variables.&lt;/p&gt;

&lt;h3&gt;
  
  
  Dockerfile
&lt;/h3&gt;

&lt;p&gt;Let's keep the &lt;code&gt;api/Dockerfile&lt;/code&gt; as simple as possible for now.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FROM clojure:openjdk-17-tools-deps-buster

WORKDIR app
COPY . .

RUN clojure -P

CMD clj -X:run

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;In general it's better to create the Dockerfile in stages to avoid unnecessary build delay and minimize the image size. If you're interested in learning more about that let me know in the comments. You can also read more &lt;a href="https://docs.docker.com/build/building/multi-stage/"&gt;Docker: Multi-Stage Builds&lt;/a&gt; for further information.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Finally, let's set up a &lt;code&gt;api/docker-compose.yml&lt;/code&gt; file for mimicking our deployment environment to test that the API is working as expected.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;version: '3.1'

services:

  api:
    build: .
    environment:
      JDBC_DATABASE_URL: "jdbc:postgresql://postgres:5432/db?user=user&amp;amp;password=password"
      PORT: 8000
    ports:
      - 8000:8000

  postgres:
    image: postgres
    environment:
      POSTGRES_DB: db
      POSTGRES_USER: user
      POSTGRES_PASSWORD: password
    ports:
      - 5432:5432

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Validate Application Code
&lt;/h3&gt;

&lt;p&gt;Start the API and the database with &lt;code&gt;docker-compose up&lt;/code&gt; and &lt;code&gt;HTTP GET localhost:8000&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; http localhost:8000
HTTP/1.1 200 OK
Content-Type: application/edn
Date: Mon, 30 Oct 2023 16:50:53 GMT
Server: Jetty(9.2.21.v20170120)
Transfer-Encoding: chunked

[{:version "PostgreSQL 14.2 (Debian 14.2-1.pgdg110+1) 
            on x86_64-pc-linux-gnu, compiled by gcc 
            (Debian 10.2.1-6) 10.2.1 20210110, 64-bit"}]

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you did get something similar to the above, we are good to continue forward. The next step is to set up the application to run in DigitalOcean.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;In the case you're wondering what's the &lt;code&gt;http&lt;/code&gt; command. It is &lt;a href="https://httpie.io/cli"&gt;httpie&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  DigitalOcean and Terraform
&lt;/h2&gt;

&lt;p&gt;The first thing we must do is to set up the DigitalOcean Terraform provider. This is analogous to adding a Clojure dependency in a &lt;code&gt;deps.edn&lt;/code&gt; file. Open &lt;code&gt;terraform/setup.tf&lt;/code&gt; and add the following content to it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;terraform {
  required_providers {
    digitalocean = {
      source = "digitalocean/digitalocean"
      version = "~&amp;gt; 2.0"
    }
  }
}

provider "digitalocean" {}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When a new provider is added, Terraform needs to be explicitly initialized with &lt;code&gt;terraform init&lt;/code&gt;. After running the command, you should see the following message with additional output.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Terraform has been successfully initialized!

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So far, we have downloaded the provider but have not yet configured the authentication. We'll get there in a moment.&lt;/p&gt;

&lt;h3&gt;
  
  
  Project
&lt;/h3&gt;

&lt;p&gt;Now it's time to set up the project to which we'll assign all our resources. Add the following content to &lt;code&gt;terraform/main.tf&lt;/code&gt; and feel free to name your project however you wish.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource "digitalocean_project" "project" {
  name = "my-sample-project"
  description = "description"
  environment = "development"
  purpose = "template-app"
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If we try to apply the changes before configuring the API token, we should see something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; terraform apply

...

digitalocean_project.project: Creating...

 Error: Error creating Project: POST https://api.digitalocean.com/v2/projects: 
| 401 (request "f2774cfc-66fc-4f34-98d9-0935f9dcd33d") 
| Unable to authenticate you with digitalocean_project.project,
        on main.tf line 1, in resource "digitalocean_project" "project":
        1: resource "digitalocean_project" "project" {

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To apply the changes, we must authenticate ourselves with a &lt;code&gt;token&lt;/code&gt;. There are a couple of different options on how to do this.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://registry.terraform.io/providers/digitalocean/digitalocean/latest/docs#token"&gt;&lt;code&gt;token&lt;/code&gt;&lt;/a&gt; - (Required) This is the DO API token. Alternatively, this can also be specified using environment variables ordered by precedence:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://registry.terraform.io/providers/digitalocean/digitalocean/latest/docs#DIGITALOCEAN_TOKEN"&gt;&lt;code&gt;DIGITALOCEAN_TOKEN&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://registry.terraform.io/providers/digitalocean/digitalocean/latest/docs#DIGITALOCEAN_ACCESS_TOKEN"&gt;&lt;code&gt;DIGITALOCEAN_ACCESS_TOKEN&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://registry.terraform.io/providers/digitalocean/digitalocean/latest/docs"&gt;DigitalOcean Terraform Provider&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I'll do this step by exporting my API token in an environment variable in the terminal.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; export DIGITALOCEAN_TOKEN=${YOUR_API_TOKEN}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After this, we should be able to create the project by running &lt;code&gt;terraform apply&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; terraform apply

Terraform used the selected providers to generate the following execution plan.
Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # digitalocean_project.project will be created
  + resource "digitalocean_project" "project" {
      + created_at = (known after apply)
      + description = "description"
      + environment = "development"
      + id = (known after apply)
      + is_default = false
      + name = "my-sample-project"
      + owner_id = (known after apply)
      + owner_uuid = (known after apply)
      + purpose = "template-app"
      + resources = (known after apply)
      + updated_at = (known after apply)
    }

Plan: 1 to add, 0 to change, 0 to destroy.

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

digitalocean_project.project: Creating...
digitalocean_project.project: Creation complete after 1s [id=22276173-77de-4e41-ab47-fae4a86ec414]

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And confirm that the project was created by logging in to the DigitalOcean dashboard.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--q3G7ycqg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn.hashnode.com/res/hashnode/image/upload/v1698853736510/172d417b-8394-47c0-bc63-78ea8cb4caf5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--q3G7ycqg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn.hashnode.com/res/hashnode/image/upload/v1698853736510/172d417b-8394-47c0-bc63-78ea8cb4caf5.png" alt="" width="800" height="681"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Container Registry
&lt;/h3&gt;

&lt;p&gt;The next step is to set up the container registry to publish the application image. Add the following to &lt;code&gt;terraform/main.tf&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; resource "digitalocean_container_registry" "app_registry" {
  name = "clojure-sample-app"
  subscription_tier_slug = "starter"
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After applying the configuration, confirm that the registry was created. Let's do it with the &lt;code&gt;doctl&lt;/code&gt; command line tool this time around.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; doctl registry get clojure-sample-app
Name Endpoint Region slug
clojure-sample-app registry.digitalocean.com/clojure-sample-app blr1

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, we need to pick up the endpoint value &lt;code&gt;clojure-sample-app&lt;/code&gt; and use that to tag the application image. Add the &lt;code&gt;build&lt;/code&gt; section to the &lt;code&gt;api/docker-compose.yml&lt;/code&gt; file and run &lt;code&gt;docker-compose build&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;services:
  api:
    build:
      context: .
      tags:
        - "registry.digitalocean.com/clojure-sample-app/dev"
    environment:
      JDBC_DATABASE_URL: "jdbc:postgresql://postgres:5432/db?user=user&amp;amp;password=password"
      PORT: 8000
    ports:
      - 8000:8000

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After building, confirm that the image exists with the expected name.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; docker image ls | grep clojure-sample-app
registry.digitalocean.com/clojure-sample-app/dev latest 20266c1dfcda 12 seconds ago 715MB

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It looks like the build produced the expected outcome. Next, we must &lt;a href="https://docs.digitalocean.com/products/container-registry/how-to/use-registry-docker-kubernetes/#docker-integration"&gt;log in&lt;/a&gt; to the DigitalOcean container registry to publish the Docker image.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; doctl registry login
Logging Docker in to registry.digitalocean.com

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And then push the image to the registry.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; docker push registry.digitalocean.com/clojure-sample-app/dev
...
latest: digest: sha256:d8a5c01cd95ab5c0981c454866ec52203feba529f169e2da93cb6a99f3af5d88 size: 2840

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's confirm with &lt;code&gt;doctl&lt;/code&gt; that the image is available in DigitalOcean.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; doctl registry repository list-v2
Name Latest Manifest Latest Tag Tag Count Manifest Count Updated At
dev sha256:d8a5c01cd95ab5c0981c454866ec52203feba529f169e2da93cb6a99f3af5d88 latest 1 1 2023-11-01 16:07:59 +0000 UTC

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this point, we have created everything required for the app deployment. Let's do that next.&lt;/p&gt;

&lt;h3&gt;
  
  
  Application
&lt;/h3&gt;

&lt;p&gt;Create a &lt;code&gt;digitalocean_app resource&lt;/code&gt; and update the &lt;code&gt;digitalocean_project&lt;/code&gt; from before by adding the app to the resources. This way, DigitalOcean can group the resources (in this case, just the app) under the project.&lt;/p&gt;

&lt;p&gt;Let's use the least expensive configuration for the dev environment by utilizing a &lt;code&gt;basic-xss&lt;/code&gt; instance with a development database. You can modify these based on your needs. See the rest of the available configuration options from the &lt;a href="https://registry.terraform.io/providers/digitalocean/digitalocean/latest/docs/resources/app#argument-reference"&gt;docs&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource "digitalocean_project" "project" {
  ...
  # Add this line
  resources = [digitalocean_app.app.urn]
}

resource "digitalocean_app" "app" {
  spec {
    name = "sample-app"
    region = "ams"

    alert {
      rule = "DEPLOYMENT_FAILED"
    }

    service {
      name = "api"
      instance_count = 1
      instance_size_slug = "basic-xxs"

      image {
        registry_type = "DOCR"
        repository = "dev"
        tag = "latest"
        deploy_on_push {
          enabled = true
        }
      }

      env {
        key = "JDBC_DATABASE_URL"
        value = "$${starter-db.JDBC_DATABASE_URL}"
      }

      source_dir = "api/"
      http_port = 8000

      run_command = "clj -X:run"
    }

    database {
      name = "starter-db"
      engine = "PG"
      production = false
    }
  }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You probably noticed that we don't have a &lt;code&gt;PORT&lt;/code&gt; variable for the application. This is because, in this case, we had configured it. Digitalocean would override it with the &lt;code&gt;http_port&lt;/code&gt; variable.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--vhGCltAc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn.hashnode.com/res/hashnode/image/upload/v1698857182445/16813ef2-1b72-48cc-bac1-918c59607c1a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--vhGCltAc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn.hashnode.com/res/hashnode/image/upload/v1698857182445/16813ef2-1b72-48cc-bac1-918c59607c1a.png" alt="" width="800" height="154"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;image&lt;/code&gt; block of the configuration ties the container registry we created earlier to the application. After, whenever we push a new image with the &lt;code&gt;dev&lt;/code&gt; tag, the application will be redeployed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Validate
&lt;/h3&gt;

&lt;p&gt;Finally, it's time to check if we did everything correctly. Let's fetch the live URL for our new application. We can do this with &lt;code&gt;doctl apps list&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; doctl apps list -o json | jq ".[0].live_url"
"https://sample-app-xeoo8.ondigitalocean.app"

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, I prefer using the resource &lt;a href="https://registry.terraform.io/providers/digitalocean/digitalocean/latest/docs/resources/app#attributes-reference"&gt;available attributes&lt;/a&gt; via &lt;a href="https://developer.hashicorp.com/terraform/language/values/outputs"&gt;Terraform outputs&lt;/a&gt;. These are usually declared in the &lt;code&gt;output.tf&lt;/code&gt; file, but I'll skip this step for the example and leave it in &lt;code&gt;terraform/main.tf&lt;/code&gt;. Add the following to the file and run &lt;code&gt;terraform apply&lt;/code&gt; once more.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;output "app_url" {
  value = digitalocean_app.app.live_url
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Afterwards, the outputs can be viewed with &lt;code&gt;terraform output&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; terraform output
app_url = "https://sample-app-xeoo8.ondigitalocean.app"

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, we are ready for the last step. Validate that we get the Postgres version as a result, as we did with the local setup.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; http https://sample-app-xeoo8.ondigitalocean.app

HTTP/1.1 200 OK
CF-Cache-Status: MISS
CF-RAY: 81f5801c8898d92e-HEL
Connection: keep-alive
Content-Encoding: br
Content-Type: application/edn
Date: Wed, 01 Nov 2023 16:26:37 GMT
Last-Modified: Wed, 01 Nov 2023 16:26:36 GMT
Server: cloudflare
Set-Cookie: __cf_bm=SHREkz9ddLOs1EoTzuNj7DSPYfazEy_bpdi7y_Kb9BE-1698855996-0-AVgy3TNvUDITYonrci23K+dW1BHozbNCLeNX0FMHMpI3miRJBq8I4HlQE5nMA5YjoZ4dEXLatcef+AE+gjq4xeQ=; path=/; expires=Wed, 01-Nov-23 16:56:36 GMT; domain=.ondigitalocean.app; HttpOnly; Secure; SameSite=None
Transfer-Encoding: chunked
Vary: Accept-Encoding
cache-control: private
x-do-app-origin: 35eedf3b-e0a9-4376-863e-c5ba59ef5d6a
x-do-orig-status: 200

[{:version "PostgreSQL 12.16 on x86_64-pc-linux-gnu, 
            compiled by gcc, a 47816671df p 0b9793bd75, 64-bit"}]

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Looks like everything is working as expected.&lt;/p&gt;

&lt;h3&gt;
  
  
  Destroy
&lt;/h3&gt;

&lt;p&gt;If you don't want to keep the application running, remember to destroy all of the created resources with &lt;code&gt;terraform destroy&lt;/code&gt;. The expected monthly cost with this setup with the &lt;code&gt;basic-xxs&lt;/code&gt; instance size and development database is around $12 per month, but the starter subscription tier of DOCR is free of charge.&lt;/p&gt;

&lt;p&gt;You can read more about the pricing models from the DigitalOcean docs: &lt;a href="https://docs.digitalocean.com/products/app-platform/details/pricing/"&gt;App platform&lt;/a&gt;, &lt;a href="https://docs.digitalocean.com/products/container-registry/details/pricing/"&gt;DOCR pricing&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;I think that DigitalOcean is an excellent lightweight alternative for smaller projects. It does have all the expected features for general app development, and it can be extended with the add-ons found in the &lt;a href="https://marketplace.digitalocean.com/"&gt;marketplace&lt;/a&gt;. I have not yet used it for long-term projects since my side projects don't last long enough, but so far, I've been happy with it from the tinkerer's perspective. I would rather not spend my leisure coding time dealing with infrastructure.&lt;/p&gt;

&lt;p&gt;Once again, thanks for reading, and I hope you found this helpful. Here's the accompanying GitHub &lt;a href="https://github.com/tvaisanen/digitalocean-terraform-clojure-template/tree/blog-post-series-01"&gt;repository&lt;/a&gt;.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Show me the Javascript!</title>
      <dc:creator>Toni</dc:creator>
      <pubDate>Fri, 27 Oct 2023 05:32:48 +0000</pubDate>
      <link>https://dev.to/tvaisanen/show-me-the-javascript-2p2b</link>
      <guid>https://dev.to/tvaisanen/show-me-the-javascript-2p2b</guid>
      <description>&lt;p&gt;When I first started learning Clojurescript, I often thought about how the code I'm writing translates to Javascript. This was because I had spent a few years prior primarily working on Typescript; hence, it was the reference I had at the time.&lt;/p&gt;

&lt;p&gt;I figured there must be an easy way to see the translation result, and I asked around what's the default way to do this in the REPL. As far as I know, I might as well have been the first one asking about this since there was no quick answer available (usually there is).&lt;/p&gt;

&lt;p&gt;Since Clojurescript is compiled into Javascript, I thought the most straightforward way to do this would be to use the plain JS interop.&lt;/p&gt;

&lt;h2&gt;
  
  
  Plain JS Interop
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;The toString() method of Function instances returns a string representing the source code of this function.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/toString"&gt;MDN Docs&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Knowing that &lt;code&gt;toString&lt;/code&gt; method prints out the source code for functions. We can view the compiled source by simply calling the &lt;code&gt;.toString&lt;/code&gt;. In Javascript, it'd be as simple as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt;&amp;gt; function piece_of_code(){ 
&amp;gt;&amp;gt; console.log("hello")
&amp;gt;&amp;gt; };
undefined
&amp;gt;&amp;gt; piece_of_code.toString()
'function piece_of_code(){ 
  console.log("hello")
}'

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The same approach works in the CLJS context, and we can make the result read better by just printing out the value.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(.toString piece-of-code)
"function cljs$user$piece_of_code(){\nreturn console.log(\"hello\");\n}"

(defn print-src [f]
  (println (str f)))

(print-src piece-of-code)
;; =&amp;gt; 
;; function app$core$piece_of_code(){
;; return console.log("hello");
;; }

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  CLJS Compiler Option
&lt;/h2&gt;

&lt;p&gt;But after some digging, I found the fantastic video: "&lt;a href="https://www.youtube.com/watch?v=kBKIGj1_WAo&amp;amp;t=10124s"&gt;Mike Fikes explains the ClojureScript Compile&lt;/a&gt;r" and learned that a dynamic CLJS compiler variable &lt;code&gt;*print-fn-bodies*&lt;/code&gt; does the same thing without the extra work. Later, I discovered that Mike also has a &lt;a href="https://blog.fikesfarm.com/posts/2017-07-29-improved-function-printing.html"&gt;blog post&lt;/a&gt; explaining how to do this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(set! *print-fn-bodies* true)

(do piece-of-code)
;; =&amp;gt; #object[app$core$piece_of_code "function app$core$piece_of_code(){
;; return console.log("hello");
;; }"]

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this approach, it is enough to set the variable, and you're good to go.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;I think it is good to know how to do this when you're working with CLJS. You might not need to drop into the JS level that often, but knowing how to do it quickly without using external tools makes all the difference in the dev flow, and even if you don't need to, it's good to know how to do it just for curiosity's sake.&lt;/p&gt;

&lt;p&gt;Ultimately, the code we are dealing with is just JavaScript, and we can use the same functions we'd be using on the JS development. On top of that, if you're working in a browser context, it's also possible to view and interact with the compiled code in the browser console.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--AGQ6s1gB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn.hashnode.com/res/hashnode/image/upload/v1698332312812/acf8f8dc-2780-4a4d-adcc-a8da542cecd3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--AGQ6s1gB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn.hashnode.com/res/hashnode/image/upload/v1698332312812/acf8f8dc-2780-4a4d-adcc-a8da542cecd3.png" alt="" width="800" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Thanks for reading. I hope you found this helpful. I recommend taking a look at &lt;a href="https://blog.fikesfarm.com/tags/ClojureScript.html"&gt;Mike's blog&lt;/a&gt; to learn more about Clojurescript.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Clojure and Cross Origin Resource Sharing (CORS)</title>
      <dc:creator>Toni</dc:creator>
      <pubDate>Sat, 21 Oct 2023 07:48:51 +0000</pubDate>
      <link>https://dev.to/tvaisanen/clojure-and-cross-origin-resource-sharing-cors-l6o</link>
      <guid>https://dev.to/tvaisanen/clojure-and-cross-origin-resource-sharing-cors-l6o</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at &lt;a href="http://localhost:8000/"&gt;http://localhost:8000/&lt;/a&gt;. (Reason: CORS header Access-Control-Allow-Origin missing). Status code: 200.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you're dealing with the above error with your Clojure web app you've come to the right place. This is a common problem when fetching data from a different origin (read URL) for single-page web applications (SPA). A common scenario is that an SPA is served from AWS S3 and it is fetching data from different servers.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Fix "The Same Origin Policy disallows reading the remote resource"
&lt;/h2&gt;

&lt;p&gt;A common fix is to add a backend middleware to deal with the CORS headers such as &lt;code&gt;ring-cors&lt;/code&gt; and this is what this post is about. Alternatively, you can serve the web app from the API's origin if possible or write your logic (i.e. middleware) to handle the CORS requests or if you're dealing with a third-party API there is likely an option to configure your allowed web origins.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configure CORS Middleware
&lt;/h2&gt;

&lt;p&gt;First, add the latest version of &lt;a href="https://clojars.org/ring-cors"&gt;ring-cors&lt;/a&gt; to your dependencies.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{:paths ["src" "resources"]
 :deps {org.clojure/clojure {:mvn/version "1.11.1"}
        ring/ring-core {:mvn/version "1.6.3"}
        ring/ring-jetty-adapter {:mvn/version "1.6.3"}
        ring-cors/ring-cors {:mvn/version "0.1.13"}}}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Require the dependency and wrap the application handler with &lt;code&gt;ring.middleware.cors/wrap.cors&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(ns tvaisanen.cors
  (:require [ring.middleware.cors :refer [wrap-cors]]
            [ring.adapter.jetty :as jetty]))

(defn app [_request]
  {:status 200
   :headers {"Content-Type" "text/html"}
   :body "OK"})

(defonce server (atom nil))

(defn start! []
  (reset! server
          (jetty/run-jetty
           (wrap-cors #'app
                      :access-control-allow-origin #"https://tvaisanen.com"
                      :access-control-allow-methods [:get :put :post :delete])
           {:port 8000 :join? false})))

(comment
  (.stop @server)
  (start!))

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's run the server and validate that it is working as expected.&lt;/p&gt;

&lt;h2&gt;
  
  
  Verify the Configuration
&lt;/h2&gt;

&lt;p&gt;Verify the configuration by making an HTTP request from the browser console and inspecting the headers from the network tab.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Send the Request&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Type this in your browser's dev tools console (from the origin configured for CORS).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fetch("http://localhost:8000").then(console.log)

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Request Headers&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You should see something similar to this in your request headers when you inspect the network tab.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GET / HTTP/1.1
Host: localhost:8000
Origin: https://tvaisanen.com
Sec-Fetch-Mode: cors
Sec-Fetch-Site: cross-site

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Response Headers&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;And the response headers are similar to this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;HTTP/1.1 200 OK
Date: Thu, 19 Oct 2023 05:53:16 GMT
Content-Type: text/html
Access-Control-Allow-Methods: DELETE, GET, POST, PUT
Access-Control-Allow-Origin: https://tvaisanen.com
Transfer-Encoding: chunked
Server: Jetty(9.2.21.v20170120)

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What is important to notice here is that the response header &lt;code&gt;Access-Control-Allow-Origin&lt;/code&gt; has the value from the requests &lt;code&gt;Origin&lt;/code&gt; header. Having this with the &lt;code&gt;Access-Control-Allow-Methods&lt;/code&gt; header tells the browser that it is okay to use the data they are asking for.&lt;/p&gt;

&lt;p&gt;Let's double-check that this is indeed what is happening from the command line.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CORS Headers Included&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When the origin header is passed and the value matches the one that is configured on the server side we get the expected &lt;code&gt;Access-Control-Headers&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; http localhost:8000 'origin:https://tvaisanen.com'

HTTP/1.1 200 OK
Access-Control-Allow-Methods: DELETE, GET, POST, PUT
Access-Control-Allow-Origin: https://tvaisanen.com
Content-Type: text/html
Date: Thu, 19 Oct 2023 06:02:10 GMT
Server: Jetty(9.2.21.v20170120)
Transfer-Encoding: chunked

OK

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;CORS Headers Missing&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If the value of the origin header does not match the configured value the access control headers should not be in the response.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; http localhost:8000 'origin:https://not-tvaisanen.com'

HTTP/1.1 200 OK
Content-Type: text/html
Date: Thu, 19 Oct 2023 06:02:18 GMT
Server: Jetty(9.2.21.v20170120)
Transfer-Encoding: chunked

OK

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Looks like everything is working as expected!&lt;/p&gt;

&lt;p&gt;There's more to cross-origin resource sharing but this should be enough to get you over the initial problem. I recommend reading related MDN Docs to learn more about the topic.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS"&gt;Cross-Origin Resource Sharing (CORS)&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS/Errors#cors_error_messages"&gt;CORS Errors&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Thanks again for reading, I hope you found this useful.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Study Smarter With AI Tools</title>
      <dc:creator>Toni</dc:creator>
      <pubDate>Wed, 04 Oct 2023 17:32:17 +0000</pubDate>
      <link>https://dev.to/tvaisanen/study-smarter-with-ai-tools-1nn9</link>
      <guid>https://dev.to/tvaisanen/study-smarter-with-ai-tools-1nn9</guid>
      <description>&lt;p&gt;The remote culture has its advantages. Especially when it comes to studying and working at the same time. It's super valuable to have access to the lecture recordings later for you to be able to do the study part outside office hours.&lt;/p&gt;

&lt;p&gt;As a resourceful engineer, this also opens up new possibilities. If you think about it, do we need to listen to the two-hour lecture when we can just transcribe the lecture into text and browse through it quickly to see if there's any additional useful information besides the source materials?&lt;/p&gt;

&lt;p&gt;The answer to this question is of course not.&lt;/p&gt;

&lt;p&gt;What I did, I fired up a Google Colab Notebook and installed Open AI's whisper.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;!pip install git+https://github.com/openai/whisper.git

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I &lt;a href="https://stackoverflow.com/questions/9913032/how-can-i-extract-audio-from-video-with-ffmpeg"&gt;extracted the audio&lt;/a&gt; from the lecture videos with &lt;code&gt;ffmpeg&lt;/code&gt; and uploaded the audio files to Drive and then loaded these files into the Notebook.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;from google.colab import drive
drive.mount('/content/drive')

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Import Whisper and load the model. See the available configurations in the &lt;a href="https://github.com/openai/whisper#available-models-and-languages"&gt;docs&lt;/a&gt;. The next step is to just iterate over the files to transcribe and enjoy the results via text search.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import whisper
import os

model = whisper.load_model("medium")
path = "drive/MyDrive/MY_DRIVE_PATH"
files = os.listdir(path)
files
# ['audio1025532515.m4a', 'audio1358476021.m4a']

for f in files:
  result = model.transcribe(f"{path}/{f}")

  with open(f"{f}.txt", "w") as f:
    f.write(result['text'])

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And that's it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;print(result["text"])
# Huomentapiv. Aloitellaan tmn pivn luentoa....

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now I have access to the transcriptions in my Drive!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;os.listdir(path)

# ['audio1025532515.m4a',
# 'audio1358476021.m4a',
# 'audio1025532515.m4a.txt',
# 'audio1358476021.m4a.txt',]

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I thought that it would be nice to share as I found this useful, I hope that you do too! Thanks for reading.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Data Validation in Clojure</title>
      <dc:creator>Toni</dc:creator>
      <pubDate>Sun, 01 Oct 2023 18:03:24 +0000</pubDate>
      <link>https://dev.to/tvaisanen/data-validation-in-clojure-2c7o</link>
      <guid>https://dev.to/tvaisanen/data-validation-in-clojure-2c7o</guid>
      <description>&lt;p&gt;&lt;a href="https://github.com/metosin/malli"&gt;Malli&lt;/a&gt; is a data-driven schema Clojure library for defining types and validating data. It can be used for example to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;type check data when received from untrusted sources i.e. validate that HTTP request bodies before writing to the database&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;define domain entities as code and generate UML diagrams&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://blog.tvaisanen.com/typescript-like-intellisense-for-clojure-functions-with-malli"&gt;generate clj-kondo types&lt;/a&gt; for static type checking in editors.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Malli uses &lt;a href="https://github.com/weavejester/hiccup"&gt;hiccup-inspired&lt;/a&gt; vector syntax for defining types. There's also a &lt;a href="https://cljdoc.org/d/metosin/malli/0.8.9/doc/readme#map-syntax"&gt;map syntax variant&lt;/a&gt; but the use case for that is to be used as an internal library representation. The vector syntax looks like this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  (def Todo
    [:map
     [:id :int]
     [:author :string]
     [:created inst?]
     [:status [:enum :todo :doing :done]]])

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we have a "Todo" schema that represents a &lt;a href="https://clojure.org/reference/data_structures#Maps"&gt;map&lt;/a&gt; with properties: id, author, created, and status. Each child of the map vector defines a property, meaning the property &lt;code&gt;:id&lt;/code&gt; is a type of &lt;code&gt;:int&lt;/code&gt; and &lt;code&gt;:created&lt;/code&gt; is a type of &lt;code&gt;instant&lt;/code&gt; and so on. All of these properties can be validated separately.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(malli.core/validate :int 1)
;; =&amp;gt; true
(malli.core/validate :int "1")
;; =&amp;gt; false
(malli.core/validate inst? (java.time.Instant/now))
;; =&amp;gt; true
(malli.core/validate [:enum :todo :doing :done] :done)
;; =&amp;gt; true

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or as a whole.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(malli.core/validate Todo
                    {:id 1 
                     :author "Toni" 
                     :created #inst "2023-09-30"
                     :status :todo})
;; =&amp;gt; true

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the case that the data is not valid we probably want to know the details on why so. For this, we can use &lt;code&gt;malli.core/explain&lt;/code&gt; and &lt;code&gt;malli.error/humanize&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(def invalid-todo
    {:id 1
     :author "Toni" 
     :created "not-an-instant"
     :status :todo})

(malli.core/validate Todo invalid-todo)
;; =&amp;gt; false
(:errors (malli.core/explain Todo invalid-todo))
;; =&amp;gt;
({:path [:created],
  :in [:created],
  :schema inst?,
  :value "not-an-instant"})

(malli.error/humanize
  (malli.core/explain Todo invalid-todo))
;; =&amp;gt; 
{:created ["should be an inst"]}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The error messages can be customized and internationalized to fit your needs. Malli has a lot more features to offer, so I encourage you to go through the &lt;a href="https://cljdoc.org/d/metosin/malli/0.13.0/doc/readme"&gt;documentation&lt;/a&gt; to learn more.&lt;/p&gt;

&lt;p&gt;Thank you for reading, I hope you found this useful. Please let me know if there's some specific Malli feature that you would like to know more about.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Typescript Like Intellisense for Clojure Functions With Malli</title>
      <dc:creator>Toni</dc:creator>
      <pubDate>Sun, 10 Sep 2023 07:09:41 +0000</pubDate>
      <link>https://dev.to/tvaisanen/typescript-like-intellisense-for-clojure-functions-with-malli-3aef</link>
      <guid>https://dev.to/tvaisanen/typescript-like-intellisense-for-clojure-functions-with-malli-3aef</guid>
      <description>&lt;p&gt;To some, it might come as a surprise that you can type Clojure functions and get static type-checking in your editor to warn you about invalid arguments, etc. This is not a feature of the language itself, it is possible via amazing open-source projects &lt;a href="https://github.com/clj-kondo/clj-kondo"&gt;CLJ Kondo&lt;/a&gt; and &lt;a href="https://cljdoc.org/d/metosin/malli/0.12.0/doc/readme"&gt;Malli&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Similarly to typing Javascript code in Typescript, we can use separate type declaration files to instruct the TS compiler and linter. In Clojure, we can generate clj-kondo type configurations for our library or import these from library authors and enhance the developer experience.&lt;/p&gt;

&lt;p&gt;In Malli, typing is similar to TS.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(ns core
  (:require [malli.core :as m]
            [malli.dev :as dev]))

(defn add [x y]
  (+ x y))

(m/=&amp;gt; add [:=&amp;gt;
           [:cat :int :int]
           :int])

;; start the instrumentation ~ type checking
(dev/start!)

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Which generates the following type definitions in the clj-kondo cache that is picked up by the editor integration to provide type hints in your editor.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{:linters
 {:unresolved-symbol {:exclude [(malli.core/=&amp;gt;)]},
  :type-mismatch
  {:namespaces
   {core
    {add {:arities {2 {:args [:int :int], :ret :int}}}}}}}}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The type definition is fairly close to the TS ones&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(m/=&amp;gt; add [:=&amp;gt; ;; type is a function
           [:cat :int :int] ;; there's two arguments of type int
           :int ;; the function returns an int
          ])


type add = (Number, Number) =&amp;gt; Number;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When we have the instrumentation turned on&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(add "2" 1) ;; evaluating this would result in following

-- Schema Error ------------------------------------------------

Invalid function arguments:

  ["2" 1]

Function Var:

  core/add

Input Schema:

  [:cat :int :int]

Errors:

  {:in [0], 
   :message "should be an integer", 
   :path [0], 
   :schema :int, 
   :value "2"}

More information:

  https://cljdoc.org/d/metosin/malli/CURRENT/doc/function-schemas

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This can also be seen in the editor even before evaluating the code.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--MPhGfPF9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn.hashnode.com/res/hashnode/image/upload/v1694329142471/4bdcd9f0-755e-4094-b247-ed2d90a8ca6d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--MPhGfPF9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn.hashnode.com/res/hashnode/image/upload/v1694329142471/4bdcd9f0-755e-4094-b247-ed2d90a8ca6d.png" alt="" width="800" height="325"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This week I was working on a small bug on how Malli generates CLJ Kondo type definitions and I thought it'd be a good moment to share some of the basics I picked up on while doing it. I haven't missed the types from TS in a long time but now I know how to configure these when needed.&lt;/p&gt;

&lt;p&gt;Thanks for reading, I hope you found this useful.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Clojure Ring Hot Reload Server on Code Changes</title>
      <dc:creator>Toni</dc:creator>
      <pubDate>Sun, 03 Sep 2023 12:34:56 +0000</pubDate>
      <link>https://dev.to/tvaisanen/clojure-ring-hot-reload-server-on-code-changes-j0h</link>
      <guid>https://dev.to/tvaisanen/clojure-ring-hot-reload-server-on-code-changes-j0h</guid>
      <description>&lt;p&gt;Many Javascript developers use &lt;strong&gt;nodemon&lt;/strong&gt; or a backend framework that provides the feature out of the box to reload their web server when code files change to have the latest code running instantly. This makes the development feedback cycle much faster than the alternative of manually restarting the server each time changes are made.&lt;/p&gt;

&lt;p&gt;In Clojure, we often re-evaluate our function in the REPL and our server will pick up the changes. There are a few little tricks to remember when doing this and a couple of utilities that we can use to make the workflow faster for ourselves.&lt;/p&gt;

&lt;p&gt;Here's an example to demonstrate this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(ns acme.reload
  (:require [ring.middleware.params]
            [ring.adapter.jetty :as jetty])
  (:gen-class))

(defn app [_request]
  {:status 200
   :headers {"Content-Type" "text/html"}
   :body "response OK"})

(defonce server (atom nil))

(defn start! []
  (reset! server
          (jetty/run-jetty app
           {:port 8080 :join? false})))

(comment
  (.stop @server)
  (start!))

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now when the server is started with the function &lt;code&gt;start!&lt;/code&gt; we can make an HTTP GET request and get the hardcoded value "response OK".&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; http :8080
HTTP/1.1 200 OK
Content-Type: text/html
Date: Sun, 03 Sep 2023 12:10:24 GMT
Server: Jetty(9.2.21.v20170120)
Transfer-Encoding: chunked

response OK

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, if we alter the response string and re-evaluate the app in reply&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(defn app [_request]
  {:status 200
   :headers {"Content-Type" "text/html"}
   :body "response still OK"})
;; =&amp;gt; #'acme.reload/app

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We will still get the same value "response OK" because the response handler is passed to the &lt;code&gt;run-jetty&lt;/code&gt; function as a value.&lt;/p&gt;

&lt;p&gt;The first simple step we can do to improve the workflow is to use the reader macro &lt;code&gt;#'&lt;/code&gt; to pass the value as a reference. Doing using this the value is resolved by reference and not as the value the reference presents at evaluation time.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;When used it will attempt to return the referenced var. This is useful when you want to talk about the reference/declaration instead of the value it represents.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://clojure.org/guides/weird_characters#_var_quote"&gt;https://clojure.org/guides/weird_characters#_var_quote&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The only thing we need to change from the code is to add &lt;code&gt;#'&lt;/code&gt; before the &lt;code&gt;app&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(defn start! []
  (reset! server
          (jetty/run-jetty #'app
           {:port 8080 :join? false})))

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By doing this Jetty will pick up the changes whenever we re-evaluate the app function. This is super helpful when working dynamically with the REPL and it is a definite productivity boost.&lt;/p&gt;

&lt;p&gt;But if you want to have the same effect when saving source code files you might want to reach out to the &lt;a href="https://ring-clojure.github.io/ring/ring.middleware.reload.html#var-wrap-reload"&gt;&lt;code&gt;wrap-reload&lt;/code&gt;&lt;/a&gt; middleware that is part of &lt;a href="https://clojars.org/ring/ring-devel"&gt;&lt;code&gt;ring-devel&lt;/code&gt;&lt;/a&gt; that can listen to changes on source files and reload the code based on this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(ns acme.reload
  (:require [ring.middleware.params]
            [ring.middleware.reload :refer [wrap-reload]]
            [ring.adapter.jetty :as jetty])
  (:gen-class))

(defn app [_request]
  {:status 200
   :headers {"Content-Type" "text/html"}
   :body "OK"})

(defonce server (atom nil))

(defn start! []
  (reset! server
          (jetty/run-jetty
           (wrap-reload #'app)
           {:port 8080 :join? false})))

(comment
  (.stop @server)
  (start!))

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By default, &lt;code&gt;wrap-reload&lt;/code&gt; listens to changes in the &lt;code&gt;src&lt;/code&gt; folder but it takes &lt;code&gt;dirs&lt;/code&gt; as an option if other folders are needed. To summarize, the development workflow can be streamlined by using the var quote &lt;code&gt;#'&lt;/code&gt; to point to vars by reference which makes the latest REPL evaluated code available right away and &lt;code&gt;wrap-reload&lt;/code&gt; middleware to hot reload the server on file changes.&lt;/p&gt;

&lt;p&gt;As usual, thanks for reading and I hope you found this useful.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Clojure Ring Hot Reloading HTML in the Browser</title>
      <dc:creator>Toni</dc:creator>
      <pubDate>Tue, 29 Aug 2023 16:40:47 +0000</pubDate>
      <link>https://dev.to/tvaisanen/clojure-ring-hot-reloading-html-in-the-browser-3l44</link>
      <guid>https://dev.to/tvaisanen/clojure-ring-hot-reloading-html-in-the-browser-3l44</guid>
      <description>&lt;p&gt;In some cases when you're developing a full-stack app you want to be able to refresh your website when static assets change automatically. For example HTML templates or CSS files.&lt;/p&gt;

&lt;p&gt;There's a nice little utility tool &lt;a href="https://clojars.org/ring-refresh"&gt;ring-refresh&lt;/a&gt; to achieve just that.&lt;/p&gt;

&lt;p&gt;Here's an example app that uses &lt;a href="https://github.com/yogthos/Selmer"&gt;Selmer&lt;/a&gt; templates to render HTML. During development we want the browser to update to have a faster feedback loop in the browser.&lt;/p&gt;

&lt;p&gt;Here's a small example of how this works!&lt;/p&gt;




&lt;p&gt;Add the dependencies.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{:paths ["src" "resources"]
 :deps {org.clojure/clojure {:mvn/version "1.11.1"}
        ring/ring-core {:mvn/version "1.6.3"}
        ring/ring-jetty-adapter {:mvn/version "1.6.3"}
        selmer/selmer {:mvn/version "1.12.59"}
        metosin/reitit {:mvn/version "0.7.0-alpha5"}
        ring-refresh/ring-refresh {:mvn/version "0.1.3"}}}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a template file &lt;code&gt;resources/templates/layout.html&lt;/code&gt;. And remember to add the &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; tag because the &lt;code&gt;ring-refresh&lt;/code&gt; will depend on that having in place already. If that's missing the reload will not work because the middleware uses a regex to recognize where to inject the hot reload script. This took me a moment to realize so just a heads up.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html&amp;gt;
    &amp;lt;head&amp;gt;
    &amp;lt;/head&amp;gt;
    &amp;lt;body&amp;gt;
        &amp;lt;div&amp;gt;hello {{name}}&amp;lt;/div&amp;gt;
        &amp;lt;div&amp;gt;
            &amp;lt;ul&amp;gt;
                {% for item in items %}
                &amp;lt;li&amp;gt;&amp;lt;strong&amp;gt;{{item|capitalize}}&amp;lt;/strong&amp;gt;&amp;lt;/li&amp;gt;
                {% endfor %}
            &amp;lt;/ul&amp;gt;
        &amp;lt;/div&amp;gt;
    &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And here's the app. Notice the &lt;code&gt;selmer.parser/ache-off!&lt;/code&gt; call if you are using extended templates.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;When rendering files Selmer will cache the compiled template. A recompile will be triggered if the last modified timestamp of the file changes. Note that changes in files referenced by the template &lt;strong&gt;will not&lt;/strong&gt; trigger a recompile. This means that if your template extends or includes other templates you must touch the file that's being rendered for changes to take effect.&lt;/p&gt;

&lt;p&gt;Alternatively you can turn caching on and off using &lt;code&gt;(selmer.parser/cache-on!)&lt;/code&gt; and &lt;code&gt;(selmer.parser/cache-off!)&lt;/code&gt; respectively.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/yogthos/Selmer#important"&gt;https://github.com/yogthos/Selmer#important&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(ns acme.app
  (:require [selmer.parser :as selmer]
            [ring.middleware.refresh :as refresh]
            [ring.middleware.params]
            [ring.adapter.jetty :as jetty])
  (:gen-class))

(selmer.parser/cache-off!)

(defn app [_request]
  {:status 200
   :headers {"Content-Type" "text/html"}
   :body (selmer/render-file "templates/layout.html"
                             {:name "World"
                              :items ["one" "two" "three" "four"]})})

(defonce server (atom nil))

(defn start! []
  (reset! server
          (jetty/run-jetty (refresh/wrap-refresh #'app)
                           {:port 8080 :join? false})))

(comment
  (.stop @server)
  (start!))

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now when you update your templates the browser should refresh the page automatically and save you a bunch of time and nerves. I hope you found this useful!&lt;/p&gt;

</description>
    </item>
    <item>
      <title>HTML Form Fields: Readonly vs. Disabled</title>
      <dc:creator>Toni</dc:creator>
      <pubDate>Wed, 16 Aug 2023 17:20:15 +0000</pubDate>
      <link>https://dev.to/tvaisanen/html-form-fields-readonly-vs-disabled-5h6c</link>
      <guid>https://dev.to/tvaisanen/html-form-fields-readonly-vs-disabled-5h6c</guid>
      <description>&lt;p&gt;I've been tinkering with the form element once again as web developers often do and lately, I've been using the FormData to parse the values and validate the collected form data with &lt;a href="https://www.metosin.fi/blog/malli/"&gt;Malli&lt;/a&gt;. I often default to using &lt;code&gt;disabled="true"&lt;/code&gt; when sketching the forms and I want to block the user from altering a static value, but this week, it hit me that read-only and disabled HTML attributes have different implications.&lt;/p&gt;

&lt;p&gt;Here's a simple example of what I mean.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(defnc app []
  (d/form
   {:id "form"}
   (d/input {:name (str :static)
             :default-value "non-mutable"})
   (d/fieldset
    {:name "demo"}
    (d/div
     (d/label "one")
     (d/input {:name (str [:data :value-1])
               :default-value "value-one"}))
    (d/div
     (d/label "two")
     (d/input {:name (str [:data :value-2])
               :default-value "value-two"})))))

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And here's a naive form parser.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  (defn naive-form-parser [^js/HTMLFormElement form]
    (reduce
     (fn [form-data [k v]]
       (let [path (edn/read-string k)]
         (if (sequential? path)
           (assoc-in form-data path v)
           (assoc form-data path v))))
     {}
     (for [entry (.entries (js/FormData. form))]
       entry)))

  (naive-form-parser form)
;; =&amp;gt; 
{:static "non-mutable", 
 :data {:value-1 "value-one", 
        :value-2 "value-two"}}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now if we set the static field as &lt;code&gt;disabled&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(d/input {:name "static" 
          :disabled true
          :default-value "non-mutable"})
;; ...

(naive-form-parser form)
;; =&amp;gt; 
{:data {:value-1 "value-one", 
        :value-2 "value-two"}}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;disabled=true&lt;/code&gt; makes the input field "invisible" to the FormData, but when using the &lt;code&gt;readonly=true&lt;/code&gt; it will be visible.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(d/input {:name "static" 
          :read-only true
          :default-value "non-mutable"})
;; ...

(naive-form-parser form)
;; =&amp;gt; 
{:static "non-mutable", 
 :data {:value-1 "value-one", 
        :value-2 "value-two"}}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And of course, this is pretty clear if you read through the docs:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The Boolean disabled attribute, when present, makes the element not mutable, focusable, or even submitted with the form. The user can neither edit nor focus on the control, nor its form control descendants.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/disabled"&gt;https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/disabled&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;These details can be easy to forget when working with React and other libraries. There's nothing new in this post. I just thought that this was curious since I don't remember paying attention before to this small detail.&lt;/p&gt;

&lt;p&gt;If you were wondering the code examples are in &lt;a href="https://github.com/lilactown/helix"&gt;Helix&lt;/a&gt;.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Clojure Ring Logging</title>
      <dc:creator>Toni</dc:creator>
      <pubDate>Sat, 12 Aug 2023 11:39:30 +0000</pubDate>
      <link>https://dev.to/tvaisanen/clojure-ring-logging-g61</link>
      <guid>https://dev.to/tvaisanen/clojure-ring-logging-g61</guid>
      <description>&lt;p&gt;A quick tutorial on how to set up request logging for a Clojure web app backend when using Ring and Jetty adapter.&lt;/p&gt;

&lt;p&gt;First, create a new project with &lt;a href="https://github.com/seancorfield/clj-new"&gt;clj-new&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;clojure -Tclj-new app :name acme/app

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And then add the &lt;strong&gt;Ring&lt;/strong&gt; dependencies to the &lt;code&gt;deps.edn&lt;/code&gt; file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt; :deps {org.clojure/clojure {:mvn/version "1.11.1"}}
---
&amp;gt; :deps {org.clojure/clojure {:mvn/version "1.11.1"}
&amp;gt; ring/ring-core {:mvn/version "1.6.3"}
&amp;gt; ring/ring-jetty-adapter {:mvn/version "1.6.3"}
&amp;gt; ring-logger/ring-logger {:mvn/version "1.1.1"}}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, set up the &lt;a href="https://github.com/clojure/tools.logging"&gt;clojure tools logging&lt;/a&gt; with &lt;strong&gt;log4j2&lt;/strong&gt; logging backend.&lt;/p&gt;

&lt;p&gt;Update the &lt;code&gt;deps.edn&lt;/code&gt; file by adding &lt;code&gt;clj-log4j2&lt;/code&gt; dependency and set&lt;code&gt;:jvm-opts&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;5c5,6
&amp;lt; ring-logger/ring-logger {:mvn/version "1.1.1"}}
---
&amp;gt; ring-logger/ring-logger {:mvn/version "1.1.1"}
&amp;gt; clj-log4j2/clj-log4j2 {:mvn/version "0.4.0"}}
7c8,9
&amp;lt; {:run-m {:main-opts ["-m" "acme.app"]}
---
&amp;gt; {:run-m {:main-opts ["-m" "acme.app"]
&amp;gt; :jvm-opts ["-Dclojure.tools.logging.factory=clojure.tools.logging.impl/log4j2-factory"]}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a file &lt;code&gt;resources/log4j2.properties&lt;/code&gt; and save the file with the following content. Read more about the configuration options from the &lt;a href="https://logging.apache.org/log4j/2.x/manual/configuration.html#Properties"&gt;here&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;status = warn
monitorInterval = 5

appender.console.type = Console
appender.console.name = STDOUT
appender.console.layout.type = PatternLayout
appender.console.layout.pattern = %date %level:%logger: %message%n%throwable

rootLogger.level = debug
rootLogger.appenderRef.stdout.ref = STDOUT

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next wrap the ring handler with the &lt;code&gt;wrap-with-logger&lt;/code&gt; middleware to log the requests. Configuration instructions can be found from the library github page.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(ns acme.app
  (:require [clojure.tools.logging :as logging]
            [ring.middleware.params]
            [ring.adapter.jetty :as jetty]
            [ring.logger :as logger])
  (:gen-class))

(def port 3000)

(defn ring-handler [_request]
  (logging/info "Info message")
  (logging/debug "Debug message")
  (logging/warn "Warn message")
  {:status 200
   :body "OK"})

(defonce server (atom nil))

(def app
  (-&amp;gt; #'ring-handler
      logger/wrap-with-logger))

(defn start! []
  (logging/info "Listening on port: " port)
  (reset! server
          (jetty/run-jetty #'app {:port port})))

(defn -main []
  (start!))

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you are ready to run the server with &lt;code&gt;clj -M:run-m&lt;/code&gt; and you should see something like this in your console when the API is called with a &lt;code&gt;POST&lt;/code&gt; request.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; clj -M:run-m
2023-08-12 14:23:50.358:INFO::main: Logging initialized @1171ms
2023-08-12 14:23:50,440 INFO:acme.app: Server starting with arguments: {}
2023-08-12 14:23:50.452:INFO:oejs.Server:main: jetty-9.2.21.v20170120
2023-08-12 14:23:50.472:INFO:oejs.ServerConnector:main: Started ServerConnector@4602f874{HTTP/1.1}{0.0.0.0:3000}
2023-08-12 14:23:50.472:INFO:oejs.Server:main: Started @1284ms
2023-08-12 14:23:51,716 INFO:ring.logger: {:request-method :post, :uri "/", :server-name "localhost", :ring.logger/type :starting}
2023-08-12 14:23:51,717 INFO:acme.app: Info message
2023-08-12 14:23:51,717 WARN:acme.app: Warn message
2023-08-12 14:23:51,717 INFO:ring.logger: {:request-method :post, :uri "/", :server-name "localhost", :ring.logger/type :finish, :status 200, :ring.logger/ms 1}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's about it for the minimal setup. Read more about advanced configuration from the sources.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://github.com/ring-clojure/ring"&gt;https://github.com/ring-clojure/ring&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://github.com/nberger/ring-logger"&gt;https://github.com/nberger/ring-logger&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Thanks for reading, hope you found this helpful.&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
