{"id":31663906,"url":"https://github.com/rgolubtsov/customers-api-proto-lite-clojure-httpkit","last_synced_at":"2026-05-04T13:34:58.208Z","repository":{"id":316923935,"uuid":"1064965866","full_name":"rgolubtsov/customers-api-proto-lite-clojure-httpkit","owner":"rgolubtsov","description":"A daemon written in Clojure, designed and intended to be run as a microservice, implementing a special Customers API prototype with a smart yet simplified data scheme.","archived":false,"fork":false,"pushed_at":"2025-10-05T16:10:47.000Z","size":73,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-10-05T18:20:41.093Z","etag":null,"topics":["clojure","customers","docker","http-kit","json","microservice","rest-api","sqlite"],"latest_commit_sha":null,"homepage":"","language":"Clojure","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/rgolubtsov.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-09-26T20:50:18.000Z","updated_at":"2025-10-03T20:00:32.000Z","dependencies_parsed_at":null,"dependency_job_id":"01fd9184-6dae-4e5e-a41d-f86829756c6a","html_url":"https://github.com/rgolubtsov/customers-api-proto-lite-clojure-httpkit","commit_stats":null,"previous_names":["rgolubtsov/customers-api-proto-lite-clojure-httpkit"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/rgolubtsov/customers-api-proto-lite-clojure-httpkit","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rgolubtsov%2Fcustomers-api-proto-lite-clojure-httpkit","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rgolubtsov%2Fcustomers-api-proto-lite-clojure-httpkit/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rgolubtsov%2Fcustomers-api-proto-lite-clojure-httpkit/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rgolubtsov%2Fcustomers-api-proto-lite-clojure-httpkit/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rgolubtsov","download_url":"https://codeload.github.com/rgolubtsov/customers-api-proto-lite-clojure-httpkit/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rgolubtsov%2Fcustomers-api-proto-lite-clojure-httpkit/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":278846211,"owners_count":26056090,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-07T02:00:06.786Z","response_time":59,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["clojure","customers","docker","http-kit","json","microservice","rest-api","sqlite"],"created_at":"2025-10-07T20:51:42.924Z","updated_at":"2026-05-04T13:34:58.200Z","avatar_url":"https://github.com/rgolubtsov.png","language":"Clojure","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Customers API Lite microservice prototype :small_orange_diamond: \u003cimg src=\"https://clojure.org/images/clojure-logo-icon-256.png\" style=\"border:0;width:32px\" alt=\"Clojure\" /\u003e\n\n**A daemon written in Clojure, designed and intended to be run as a microservice,\n\u003cbr /\u003eimplementing a special Customers API prototype with a smart yet simplified data scheme**\n\n**Rationale:** This project is a *direct* **[Clojure](https://clojure.org \"The Clojure Programming Language | or simply Lisp-1 dialect for the JVM\")** port of the earlier developed **Customers API Lite microservice prototype**, written in Crystal using **[Kemal](https://kemalcr.com \"Lightning Fast, Super Simple Web Framework for Crystal\")** web framework, and tailored to be run as a microservice in a Docker container. The following description of the underlying architecture and logics has been taken **[from here](https://github.com/rgolubtsov/customers-api-proto-lite-crystal-kemal/blob/main/README.md)** almost as is, without any principal modifications or adjustment.\n\nThis repo is dedicated to develop a microservice that implements a prototype of REST API service for ordinary Customers operations like adding/retrieving a Customer to/from the database, also doing the same ops with Contacts (phone or email) which belong to a Customer account.\n\nThe data scheme chosen is very simplified and consisted of only three SQL database tables, but that's quite sufficient because the service operates on only two entities: a **Customer** and a **Contact** (phone or email). And a set of these operations is limited to the following ones:\n\n* Create a new customer (put customer data to the database).\n* Create a new contact for a given customer (put a contact regarding a given customer to the database).\n* Retrieve from the database and list all customer profiles.\n* Retrieve profile details for a given customer from the database.\n* Retrieve from the database and list all contacts associated with a given customer.\n* Retrieve from the database and list all contacts of a given type associated with a given customer.\n\nAs it is clearly seen, there are no *mutating*, usually expected operations like *update* or *delete* an entity and that's made intentionally.\n\nThe microservice incorporates the **[SQLite](https://sqlite.org \"A small, fast, self-contained, high-reliability, full-featured, SQL database engine\")** database as its persistent store. It is located in the `data/db/` directory as an XZ-compressed database file with minimal initial data \u0026mdash; actually having two Customers and by six Contacts for each Customer. The database file is automatically decompressed during build process of the microservice and ready to use as is even when containerized with Docker.\n\nGenerally speaking, this project might be explored as a PoC (proof of concept) on how to amalgamate Clojure REST API service backed by SQLite database, running standalone as a conventional daemon in host or VM environment, or in a containerized form as usually widely adopted nowadays.\n\nSurely, one may consider this project to be suitable for a wide variety of applied areas and may use this prototype as: (1) a template for building similar microservices, (2) for evolving it to make something more universal, or (3) to simply explore it and take out some snippets and techniques from it for *educational purposes*, etc.\n\n---\n\n## Table of Contents\n\n* **[Building](#building)**\n  * **[Creating a Docker image](#creating-a-docker-image)**\n* **[Running](#running)**\n  * **[Running a Docker image](#running-a-docker-image)**\n  * **[Exploring a Docker image payload](#exploring-a-docker-image-payload)**\n* **[Consuming](#consuming)**\n  * **[Logging](#logging)**\n  * **[Error handling](#error-handling)**\n\n## Building\n\nThe microservice might be built and run successfully under **Ubuntu Server (Ubuntu 24.04.4 LTS x86-64)** and **Arch Linux** (both proven). \u0026mdash; First install the necessary dependencies (`openjdk-21-jdk-headless`, `leiningen`, `make`, `docker.io`):\n\n* In Ubuntu Server:\n\n```\n$ sudo apt-get update \u0026\u0026 \\\n  sudo apt-get install openjdk-21-jdk-headless leiningen make docker.io -y\n...\n```\n\n* In Arch Linux:\n\n```\n$ sudo pacman -Syu jdk21-openjdk leiningen make docker\n...\n```\n\n---\n\n**Build** the microservice using **Leiningen**:\n\n```\n$ lein clean\n$\n$ lein compile :all\nCompiling customers.api-lite.controller\nCompiling customers.api-lite.core\nCompiling customers.api-lite.helper\nCompiling customers.api-lite.model\n$\n$ lein uberjar \u0026\u0026 \\\n  UBERJAR_DIR=\"target/uberjar\"; \\\n  DAEMON_NAME=\"customers-api-lite\"; \\\n  DMN_VERSION=\"0.3.0\"; \\\n  SIMPLE_JAR=\"${UBERJAR_DIR}/${DAEMON_NAME}-${DMN_VERSION}.jar\"; \\\n  BUNDLE_JAR=\"${UBERJAR_DIR}/${DAEMON_NAME}-${DMN_VERSION}-standalone.jar\"; \\\n  rm ${SIMPLE_JAR} \u0026\u0026 mv ${BUNDLE_JAR} ${SIMPLE_JAR} \u0026\u0026 \\\n  DB_DIR=\"data/db\"; \\\n  if [ -f ${DB_DIR}/${DAEMON_NAME}.db.xz ]; then \\\n     unxz ${DB_DIR}/${DAEMON_NAME}.db.xz; \\\n  fi\nCompiling customers.api-lite.controller\nCompiling customers.api-lite.core\nCompiling customers.api-lite.helper\nCompiling customers.api-lite.model\nCreated $HOME/customers-api-proto-lite-clojure-httpkit/target/uberjar/customers-api-lite-0.3.0.jar\nCreated $HOME/customers-api-proto-lite-clojure-httpkit/target/uberjar/customers-api-lite-0.3.0-standalone.jar\n```\n\nOr **build** the microservice using **GNU Make** (optional, but for convenience \u0026mdash; it covers the same **Leiningen** build workflow under the hood):\n\n```\n$ make clean\n...\n$ make      # \u003c== Compilation only phase (JVM classes).\n...\n$ make all  # \u003c== Building the daemon (executable JAR bundle).\n...\n```\n\n### Creating a Docker image\n\n**Build** a Docker image for the microservice:\n\n```\n$ # Pull the Azul Zulu JRE image first (based on Alpine Linux), if not already there:\n$ sudo docker pull azul/zulu-openjdk-alpine:21-jre-headless-latest\n...\n$ # Then build the microservice image:\n$ sudo docker build -tcustomersapi/api-lite-clj .\n...\n```\n\n## Running\n\n**Run** the microservice using **Leiningen** (recompiling sources on-the-fly, if required):\n\n```\n$ lein run; echo $?\n...\n```\n\n**Run** the microservice using its all-in-one JAR bundle, built previously by the `uberjar` Leiningen task or GNU Make's `all` target:\n\n```\n$ java -jar target/uberjar/customers-api-lite-0.3.0.jar; echo $?\n...\n```\n\nTo run the microservice as a *true* daemon, i.e. in the background, redirecting all the console output to `/dev/null`, the following form of invocation of its executable JAR bundle can be used:\n\n```\n$ java -jar target/uberjar/customers-api-lite-0.3.0.jar \u003e /dev/null 2\u003e\u00261 \u0026\n[1] \u003cpid\u003e\n```\n\n**Note:** This will suppress all the console output only; logging to a logfile and to the Unix syslog will remain unchanged.\n\nThe daemonized microservice then can be stopped gracefully at any time by issuing the following command:\n\n```\n$ kill -SIGTERM \u003cpid\u003e\n$\n[1]+  Exit 143                java -jar target/uberjar/customers-api-lite-0.3.0.jar \u003e /dev/null 2\u003e\u00261\n```\n\n### Running a Docker image\n\n**Run** a Docker image of the microservice, deleting all stopped containers prior to that (if any):\n\n```\n$ sudo docker rm `sudo docker ps -aq`; \\\n  export PORT=8765 \u0026\u0026 sudo docker run -dp${PORT}:${PORT} --name api-lite-clj customersapi/api-lite-clj; echo $?\n...\n```\n\n### Exploring a Docker image payload\n\nThe following is not necessary but might be considered somewhat interesting \u0026mdash; to look into the running container and check out that the microservice's executable JAR bundle, logfile, and accompanied SQLite database are at their expected places and in effect:\n\n```\n$ sudo docker ps -a\nCONTAINER ID   IMAGE                       COMMAND                   CREATED              STATUS              PORTS                                         NAMES\n\u003ccontainer_id\u003e customersapi/api-lite-clj   \"java -jar api-lite...\"   About a minute ago   Up About a minute   0.0.0.0:8765-\u003e8765/tcp, [::]:8765-\u003e8765/tcp   api-lite-clj\n$\n$ sudo docker exec -it api-lite-clj sh; echo $?\n/var/tmp/api-lite $\n/var/tmp/api-lite $ uname -a\nLinux \u003ccontainer_id\u003e 6.8.0-100-generic #100-Ubuntu SMP PREEMPT_DYNAMIC Tue Jan 13 16:40:06 UTC 2026 x86_64 Linux\n/var/tmp/api-lite $\n/var/tmp/api-lite $ cat /etc/os-release /etc/alpine-release\nNAME=\"Alpine Linux\"\nID=alpine\nVERSION_ID=3.20.9\nPRETTY_NAME=\"Alpine Linux v3.20\"\nHOME_URL=\"https://alpinelinux.org/\"\nBUG_REPORT_URL=\"https://gitlab.alpinelinux.org/alpine/aports/-/issues\"\n3.20.9\n/var/tmp/api-lite $\n/var/tmp/api-lite $ java --version\nopenjdk 21.0.10 2026-01-20 LTS\nOpenJDK Runtime Environment Zulu21.48+17-CA (build 21.0.10+7-LTS)\nOpenJDK 64-Bit Server VM Zulu21.48+17-CA (build 21.0.10+7-LTS, mixed mode, sharing)\n/var/tmp/api-lite $\n/var/tmp/api-lite $ ls -al\ntotal 26348\ndrwxr-xr-x    1 daemon   daemon        4096 Feb 15 18:00 .\ndrwxrwxrwt    1 root     root          4096 Feb 15 10:10 ..\n-rw-rw-r--    1 daemon   daemon    26949863 Feb 15 10:00 api-lite.jar\ndrwxr-xr-x    1 daemon   daemon        4096 Feb 15 10:10 data\ndrwxr-xr-x    2 daemon   daemon        4096 Feb 15 18:00 log\n/var/tmp/api-lite $\n/var/tmp/api-lite $ ls -al data/db/ log/\ndata/db/:\ntotal 40\ndrwxr-xr-x    1 daemon   daemon        4096 Feb 15 10:10 .\ndrwxr-xr-x    1 daemon   daemon        4096 Feb 15 10:10 ..\n-rw-rw-r--    1 daemon   daemon       24576 Feb 15 09:40 customers-api-lite.db\n\nlog/:\ntotal 16\ndrwxr-xr-x    2 daemon   daemon        4096 Feb 15 18:00 .\ndrwxr-xr-x    1 daemon   daemon        4096 Feb 15 18:00 ..\n-rw-r--r--    1 daemon   daemon         454 Feb 15 18:00 customers-api-lite.log\n/var/tmp/api-lite $\n/var/tmp/api-lite $ netstat -plunt\nActive Internet connections (only servers)\nProto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name\ntcp        0      0 :::8765                 :::*                    LISTEN      1/java\n/var/tmp/api-lite $\n/var/tmp/api-lite $ ps aux\nPID   USER     TIME  COMMAND\n    1 daemon    0:10 java -jar api-lite.jar\n   25 daemon    0:00 sh\n   48 daemon    0:00 ps aux\n/var/tmp/api-lite $\n/var/tmp/api-lite $ exit # Or simply \u003cCtrl-D\u003e.\n0\n```\n\nTo stop a running container of the microservice gracefully at any time, simply issue the following command:\n\n```\n$ sudo docker stop api-lite-clj; echo $?\napi-lite-clj\n0\n```\n\n## Consuming\n\nThe microservice exposes **six REST API endpoints** to web clients. They are all intended to deal with customer entities and/or contact entities that belong to customer profiles. The following table displays their syntax:\n\nNo. | Endpoint name                                      | Request method and REST URI                                   | Request body\n--: | -------------------------------------------------- | ------------------------------------------------------------- | ----------------------------------------------------------------\n1   | Create customer                                    | **PUT** `/v1/customers`                                       | `{\"name\":\"{customer_name}\"}`\n2   | Create contact                                     | **PUT** `/v1/customers/contacts`                              | `{\"customer_id\":\"{customer_id}\",\"contact\":\"{customer_contact}\"}`\n3   | List customers                                     | **GET** `/v1/customers`                                       | \u0026ndash;\n4   | Retrieve customer                                  | **GET** `/v1/customers/{customer_id}`                         | \u0026ndash;\n5   | List contacts for a given customer                 | **GET** `/v1/customers/{customer_id}/contacts`                | \u0026ndash;\n6   | List contacts of a given type for a given customer | **GET** `/v1/customers/{customer_id}/contacts/{contact_type}` | \u0026ndash;\n\n* The `{customer_name}` placeholder is a string \u0026mdash; it usually means the full name given to a newly created customer.\n* The `{customer_id}` placeholder is a decimal positive integer number, greater than `0`.\n* The `{customer_contact}` placeholder is a string \u0026mdash; it denotes a newly created customer contact (phone or email).\n* The `{contact_type}` placeholder is a string and can take one of two possible values, case-insensitive: `phone` or `email`.\n\nThe following command-line snippets display the exact usage for these endpoints (the **cURL** utility is used as an example to access them)^:\n\n1. **Create customer**\n\n```\n$ curl -vXPUT http://localhost:8765/v1/customers \\\n       -H 'content-type: application/json' \\\n       -d '{\"name\":\"Jamison Palmer\"}'\n...\n\u003e PUT /v1/customers HTTP/1.1\n...\n\u003e content-type: application/json\n\u003e Content-Length: 25\n...\n\u003c HTTP/1.1 201 Created\n\u003c Content-Type: application/json\n\u003c Location: /v1/customers/3\n\u003c content-length: 32\n\u003c Server: http-kit\n...\n{\"id\":3,\"name\":\"Jamison Palmer\"}\n```\n\n2. **Create contact**\n\n```\n$ curl -vXPUT http://localhost:8765/v1/customers/contacts \\\n       -H 'content-type: application/json' \\\n       -d '{\"customer_id\":\"3\",\"contact\":\"+12197654320\"}'\n...\n\u003e PUT /v1/customers/contacts HTTP/1.1\n...\n\u003e content-type: application/json\n\u003e Content-Length: 44\n...\n\u003c HTTP/1.1 201 Created\n\u003c Content-Type: application/json\n\u003c Location: /v1/customers/3/contacts/phone\n\u003c content-length: 26\n\u003c Server: http-kit\n...\n{\"contact\":\"+12197654320\"}\n```\n\nOr create **email** contact:\n\n```\n$ curl -vXPUT http://localhost:8765/v1/customers/contacts \\\n       -H 'content-type: application/json' \\\n       -d '{\"customer_id\":\"3\",\"contact\":\"jamison.palmer@example.com\"}'\n...\n\u003e PUT /v1/customers/contacts HTTP/1.1\n...\n\u003e content-type: application/json\n\u003e Content-Length: 58\n...\n\u003c HTTP/1.1 201 Created\n\u003c Content-Type: application/json\n\u003c Location: /v1/customers/3/contacts/email\n\u003c content-length: 40\n\u003c Server: http-kit\n...\n{\"contact\":\"jamison.palmer@example.com\"}\n```\n\n3. **List customers**\n\n```\n$ curl -v http://localhost:8765/v1/customers\n...\n\u003e GET /v1/customers HTTP/1.1\n...\n\u003c HTTP/1.1 200 OK\n\u003c Content-Type: application/json\n\u003c content-length: 136\n\u003c Server: http-kit\n...\n[{\"id\":1,\"name\":\"Jammy Jellyfish\"},{\"id\":2,\"name\":\"Noble Numbat\"},{\"id\":3,\"name\":\"Jamison Palmer\"},{\"id\":4,\"name\":\"Sarah Kitteringham\"}]\n```\n\n4. **Retrieve customer**\n\n```\n$ curl -v http://localhost:8765/v1/customers/3\n...\n\u003e GET /v1/customers/3 HTTP/1.1\n...\n\u003c HTTP/1.1 200 OK\n\u003c Content-Type: application/json\n\u003c content-length: 32\n\u003c Server: http-kit\n...\n{\"id\":3,\"name\":\"Jamison Palmer\"}\n```\n\n5. **List contacts for a given customer**\n\n```\n$ curl -v http://localhost:8765/v1/customers/3/contacts\n...\n\u003e GET /v1/customers/3/contacts HTTP/1.1\n...\n\u003c HTTP/1.1 200 OK\n\u003c Content-Type: application/json\n\u003c content-length: 186\n\u003c Server: http-kit\n...\n[{\"contact\":\"+12197654320\"},{\"contact\":\"+12197654321\"},{\"contact\":\"+12197654322\"},{\"contact\":\"jamison.palmer@example.com\"},{\"contact\":\"jp@example.com\"},{\"contact\":\"jpalmer@example.com\"}]\n```\n\n6. **List contacts of a given type for a given customer**\n\n```\n$ curl -v http://localhost:8765/v1/customers/3/contacts/phone\n...\n\u003e GET /v1/customers/3/contacts/phone HTTP/1.1\n...\n\u003c HTTP/1.1 200 OK\n\u003c Content-Type: application/json\n\u003c content-length: 82\n\u003c Server: http-kit\n...\n[{\"contact\":\"+12197654320\"},{\"contact\":\"+12197654321\"},{\"contact\":\"+12197654322\"}]\n```\n\nOr list **email** contacts:\n\n```\n$ curl -v http://localhost:8765/v1/customers/3/contacts/email\n...\n\u003e GET /v1/customers/3/contacts/email HTTP/1.1\n...\n\u003c HTTP/1.1 200 OK\n\u003c Content-Type: application/json\n\u003c content-length: 105\n\u003c Server: http-kit\n...\n[{\"contact\":\"jamison.palmer@example.com\"},{\"contact\":\"jpalmer@example.com\"},{\"contact\":\"jp@example.com\"}]\n```\n\n\u003e ^ The given names in customer accounts and in email contacts (in samples above) are for demonstrational purposes only. They have nothing common WRT any actual, ever really encountered names elsewhere.\n\n### Logging\n\nThe microservice has the ability to log messages to a logfile and to the Unix syslog facility. To enable debug logging, the `:logger.debug.enabled` setting in the microservice main config file `etc/settings.conf` should be set to `true` *before building the microservice*. When running under Ubuntu Server or Arch Linux (not in a Docker container), logs can be seen and analyzed in an ordinary fashion, by `tail`ing the `log/customers-api-lite.log` logfile:\n\n```\n$ tail -f log/customers-api-lite.log\n[2026-02-15][15:10:00] [DEBUG] [Customers API Lite]\n[2026-02-15][15:10:00] [INFO ] HikariPool-1 - Starting...\n[2026-02-15][15:10:00] [INFO ] HikariPool-1 - Added connection org.sqlite.jdbc4.JDBC4Connection@7109b603\n[2026-02-15][15:10:00] [INFO ] HikariPool-1 - Start completed.\n[2026-02-15][15:10:00] [DEBUG] [HikariProxyConnection@1040733616 wrapping org.sqlite.jdbc4.JDBC4Connection@7109b603]\n[2026-02-15][15:10:00] [INFO ] Server started on port 8765\n[2026-02-15][15:10:30] [DEBUG] [PUT]\n[2026-02-15][15:10:30] [DEBUG] [Saturday Sunday]\n[2026-02-15][15:10:30] [DEBUG] [5|Saturday Sunday]\n[2026-02-15][15:10:50] [DEBUG] [PUT]\n[2026-02-15][15:10:50] [DEBUG] customer_id=5\n[2026-02-15][15:10:50] [DEBUG] [Saturday.Sunday@example.com]\n[2026-02-15][15:10:50] [DEBUG] [email|Saturday.Sunday@example.com]\n[2026-02-15][15:11:10] [DEBUG] [GET]\n[2026-02-15][15:11:10] [DEBUG] customer_id=5\n[2026-02-15][15:11:10] [DEBUG] [5|Saturday Sunday]\n[2026-02-15][15:11:40] [DEBUG] [GET]\n[2026-02-15][15:11:40] [DEBUG] customer_id=5 | contact_type=email\n[2026-02-15][15:11:40] [DEBUG] [Saturday.Sunday@example.com]\n[2026-02-15][15:12:00] [INFO ] Server stopped\n[2026-02-15][15:12:00] [INFO ] HikariPool-1 - Shutdown initiated...\n[2026-02-15][15:12:00] [INFO ] HikariPool-1 - Shutdown completed.\n```\n\nMessages registered by the Unix system logger can be seen and analyzed using the `journalctl` utility:\n\n```\n$ journalctl -f\n...\nFeb 15 15:10:00 \u003chostname\u003e java[\u003cpid\u003e]: [Customers API Lite]\nFeb 15 15:10:00 \u003chostname\u003e java[\u003cpid\u003e]: [HikariProxyConnection@1040733616 wrapping org.sqlite.jdbc4.JDBC4Connection@7109b603]\nFeb 15 15:10:00 \u003chostname\u003e java[\u003cpid\u003e]: Server started on port 8765\nFeb 15 15:10:30 \u003chostname\u003e java[\u003cpid\u003e]: [PUT]\nFeb 15 15:10:30 \u003chostname\u003e java[\u003cpid\u003e]: [Saturday Sunday]\nFeb 15 15:10:30 \u003chostname\u003e java[\u003cpid\u003e]: [5|Saturday Sunday]\nFeb 15 15:10:50 \u003chostname\u003e java[\u003cpid\u003e]: [PUT]\nFeb 15 15:10:50 \u003chostname\u003e java[\u003cpid\u003e]: customer_id=5\nFeb 15 15:10:50 \u003chostname\u003e java[\u003cpid\u003e]: [Saturday.Sunday@example.com]\nFeb 15 15:10:50 \u003chostname\u003e java[\u003cpid\u003e]: [email|Saturday.Sunday@example.com]\nFeb 15 15:11:10 \u003chostname\u003e java[\u003cpid\u003e]: [GET]\nFeb 15 15:11:10 \u003chostname\u003e java[\u003cpid\u003e]: customer_id=5\nFeb 15 15:11:10 \u003chostname\u003e java[\u003cpid\u003e]: [5|Saturday Sunday]\nFeb 15 15:11:40 \u003chostname\u003e java[\u003cpid\u003e]: [GET]\nFeb 15 15:11:40 \u003chostname\u003e java[\u003cpid\u003e]: customer_id=5 | contact_type=email\nFeb 15 15:11:40 \u003chostname\u003e java[\u003cpid\u003e]: [Saturday.Sunday@example.com]\nFeb 15 15:12:00 \u003chostname\u003e java[\u003cpid\u003e]: Server stopped\n```\n\nInside the running container logs might be queried also by `tail`ing the `log/customers-api-lite.log` logfile:\n\n```\n/var/tmp/api-lite $ tail -f log/customers-api-lite.log\n[2026-02-15][18:00:15] [DEBUG] [Customers API Lite]\n[2026-02-15][18:00:15] [INFO ] HikariPool-1 - Starting...\n[2026-02-15][18:00:15] [INFO ] HikariPool-1 - Added connection org.sqlite.jdbc4.JDBC4Connection@50bb1c1f\n[2026-02-15][18:00:15] [INFO ] HikariPool-1 - Start completed.\n[2026-02-15][18:00:15] [DEBUG] [HikariProxyConnection@1540140763 wrapping org.sqlite.jdbc4.JDBC4Connection@50bb1c1f]\n[2026-02-15][18:00:15] [INFO ] Server started on port 8765\n[2026-02-15][18:10:20] [DEBUG] [PUT]\n[2026-02-15][18:10:20] [DEBUG] [Saturday Sunday]\n[2026-02-15][18:10:20] [DEBUG] [5|Saturday Sunday]\n[2026-02-15][18:10:25] [DEBUG] [PUT]\n[2026-02-15][18:10:25] [DEBUG] customer_id=5\n[2026-02-15][18:10:25] [DEBUG] [Saturday.Sunday@example.com]\n[2026-02-15][18:10:25] [DEBUG] [email|Saturday.Sunday@example.com]\n[2026-02-15][18:10:30] [DEBUG] [GET]\n[2026-02-15][18:10:30] [DEBUG] customer_id=5\n[2026-02-15][18:10:30] [DEBUG] [5|Saturday Sunday]\n[2026-02-15][18:10:35] [DEBUG] [GET]\n[2026-02-15][18:10:35] [DEBUG] customer_id=5 | contact_type=email\n[2026-02-15][18:10:35] [DEBUG] [Saturday.Sunday@example.com]\n```\n\nAnd of course, Docker itself gives the possibility to read log messages by using the corresponding command for that:\n\n```\n$ sudo docker logs -f api-lite-clj\n[2026-02-15][18:00:15] [DEBUG] [Customers API Lite]\n[2026-02-15][18:00:15] [INFO ] HikariPool-1 - Starting...\n[2026-02-15][18:00:15] [INFO ] HikariPool-1 - Added connection org.sqlite.jdbc4.JDBC4Connection@50bb1c1f\n[2026-02-15][18:00:15] [INFO ] HikariPool-1 - Start completed.\n[2026-02-15][18:00:15] [DEBUG] [HikariProxyConnection@1540140763 wrapping org.sqlite.jdbc4.JDBC4Connection@50bb1c1f]\n[2026-02-15][18:00:15] [INFO ] Server started on port 8765\n[2026-02-15][18:10:20] [DEBUG] [PUT]\n[2026-02-15][18:10:20] [DEBUG] [Saturday Sunday]\n[2026-02-15][18:10:20] [DEBUG] [5|Saturday Sunday]\n[2026-02-15][18:10:25] [DEBUG] [PUT]\n[2026-02-15][18:10:25] [DEBUG] customer_id=5\n[2026-02-15][18:10:25] [DEBUG] [Saturday.Sunday@example.com]\n[2026-02-15][18:10:25] [DEBUG] [email|Saturday.Sunday@example.com]\n[2026-02-15][18:10:30] [DEBUG] [GET]\n[2026-02-15][18:10:30] [DEBUG] customer_id=5\n[2026-02-15][18:10:30] [DEBUG] [5|Saturday Sunday]\n[2026-02-15][18:10:35] [DEBUG] [GET]\n[2026-02-15][18:10:35] [DEBUG] customer_id=5 | contact_type=email\n[2026-02-15][18:10:35] [DEBUG] [Saturday.Sunday@example.com]\n[2026-02-15][18:20:40] [INFO ] Server stopped\n[2026-02-15][18:20:40] [INFO ] HikariPool-1 - Shutdown initiated...\n[2026-02-15][18:20:40] [INFO ] HikariPool-1 - Shutdown completed.\n```\n\n### Error handling\n\nWhen the URI path or request body passed in an incoming request contains inappropriate input, the microservice will respond with the **HTTP 400 Bad Request** status code, including a specific response body in JSON representation which may describe a possible cause of underlying client error, like the following:\n\n```\n$ curl http://localhost:8765/v1/customers/=qwerty4838=-i-.--089asdf..nj524987\n{\"error\":\"HTTP 400 Bad Request: Request is malformed. Please check your inputs.\"}\n$\n$ curl http://localhost:8765/v1/customers/3....7/contacts\n{\"error\":\"HTTP 400 Bad Request: Request is malformed. Please check your inputs.\"}\n$\n$ curl http://localhost:8765/v1/customers/--089asdf../contacts/email\n{\"error\":\"HTTP 400 Bad Request: Request is malformed. Please check your inputs.\"}\n$\n$ curl -XPUT http://localhost:8765/v1/customers/contacts \\\n       -H 'content-type: application/json' \\\n       -d '{\"customer_id\":\"3\",\"contact\":\"12197654320--089asdf../nj524987\"}'\n{\"error\":\"HTTP 400 Bad Request: Request is malformed. Please check your inputs.\"}\n```\n\n---\n\n:new_moon:\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frgolubtsov%2Fcustomers-api-proto-lite-clojure-httpkit","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frgolubtsov%2Fcustomers-api-proto-lite-clojure-httpkit","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frgolubtsov%2Fcustomers-api-proto-lite-clojure-httpkit/lists"}