Yohoho - A take-home assignment for HolidayPirates
This is my take on the take-home assignment I was given for the Backend Engineer position at HolidayPirates.
The goal is to implement a very small REST API in Clojure. That API provides two endpoints :
POST /items: creates an itemGET /items: returns the list of items
This was a great opportunity for me to learn more about Clojure. Whatever the result of the recruitment process, it was really fun working on this assignment!
Running and testing
This project uses Leiningen. Assuming you already have Leiningen installed,
running the API should be as easy as cloning the repo and issuing lein run. By
default, the server listens on port 3000. To explore the API using Swagger UI,
you can head to http://localhost:3000/doc.
To run the unit and integration tests: lein test
Structure
The source code lies in /src/yohoho and is structured as follows:
app.clj: ring app and main entrypointconfig.clj: configuration (database and HTTP server)db.clj: functions that directly interact with the databasehandlers.clj: route handlershelpers.clj: the infamous file that holds function that are useful but couldn't fit anywhere elseroutes.clj: API routesschemas.cls: Malli schemas used for validation
Library/tools choices
Since this is just a small assignment, I sticked to simple and proven tools:
- SQLite for the database (easy to setup, easy to use with
next.jdbc) - Leiningen for managing the project (it seems to be getting outdated in favor of Clojure CLI Tools, but is still widely used and I could find more documentation)
- Libraries :
reititfor route handling +mallifor validation +muuntajafor handling json content. Thanks for the suggestions in the assignment! I chosereititbecause it seemed more complete/integrated than Compojure. However, in the context of this simple assignment, Compojure may have been a better choice (would have probably been easier to learn) - Web server:
jetty(seems to be pretty standard)
Comments
Respect of the assignment:
- The API features the
POST /itemsandGET /itemsendpoints POST /itemsaccepts a JSON payload, validates it and returns HTTP 400 is validation failsPOST /itemspersists data in SQLiteGET /itemsreturns the list of all items as JSON.- The structure of the items is respected, although it just includes the bare minimum (id, name, email).
- A data validation library is used (
malli) - Unit tests were written (although generated with the help of Claude Code)
- Integration tests were written (although generated with the help of Claude Code too)
- The API is documented (OpenAPI
/openapi.json+ Swagger UI at/doc)
Divergences from the assignment:
- The
POST /itemsdo not take the id in the input. Instead, I prefer to let the database generate a new id on its own. - As a consequence, the
idof an item is an integer, not a string. SQLite can autogenerate/autoincrement only on a integer id.
What was not part of the assignment:
GET /ahoyhealth check endpoint. As you may guess, this was my very first endpoint as I was learning how to usereitit. I decided to keep it, because a health check endpoint cannot hurt.GET /item/:idwas included. This was quite easy to add, and also makes sense since thePOST /itemsendpoint follows standard practice of returning aLocationheader for the newly created item.
Security:
- SQL injections: we should be ok since we are not using raw SQL statements in
db.clj(next.jdbcwill use parametrized statements under the hood) POST /itemsinput has validation- See the "Desirable improvement" section for some missing security features
Desirable improvements
This project is voluntarily kept simple. If it were to get deployed in production, the following should be dealt with, in no particular order:
GET /items: add pagination- Security: Add rate limiting
- Security: Add authentication/authorization
- Security: Add and configure CORS middleware
- Add logging
- TLS: it's fine that the API is accessible only through http, but it should be deployed behind a reverse proxy to add TLS/https support. This is quite easy to do with Apache, Nginx or Caddy.
- Database: use a migration system. This was definitely unnecessary for this assignment, but should be used for a more serious project. Migratus seems to be the de-facto library for this.
- Database: switch to a more serious database (PostgreSQL comes to mind), add connection pooling, use transactions.
- Add metrics (request rate, response time, database connection pool stats, etc.) and monitoring (eg. using Datadog).
- Configuration: add the ability to override deffault configuration using a dotenv file and/or environment variables.
- Add a supervisor to ensure the API keeps running (could be as easy as using systemd, or as complex as deploying with kubernetes)
Documentation links
The following links proved more than useful when working on this assignment:
- https://practical.li/clojure-web-services/building-api/
- https://github.com/metosin/reitit/blob/master/doc/ring/content_negotiation.md
- https://ostash.dev/posts/2021-08-22-data-validation-in-clojure/
- https://clojurecivitas.github.io/malli/elements_of_malli.html
- https://github.com/metosin/reitit/blob/master/doc/coercion/malli_coercion.md
- https://cljdoc.org/d/com.github.seancorfield/next.jdbc/1.3.1086/doc/getting-started
- https://cljdoc.org/d/metosin/reitit/0.10.0/doc/ring/swagger-support
- https://www.baeldung.com/clojure-ring
- https://practical.li/clojure/testing/unit-testing/