I like clojure. I also like noir, luminus create-react-app and rails. The clojure community has made a ton of great libraries that are all very simple. Unfortunately when I started putting them together, it took me a lot of time to understand the different decisions each library author made and how they worked together to get to a place where I had a working scaffold for my own bespoke full stack framework. I had some success doing this, but it still felt like I was playing catch up to all the great stuff other programming communities had. Not to mention every time I needed something else, like server reloading on each request, or refreshing code from the repl, it was another dependency and another namespace and another set of docs to read. So after slapping dependencies together in my project.clj for the nth time, I made a clojure library and a leiningen template to help me launch projects faster. I should warn you, this isn’t the first time I’ve tried this but it is the most comprehensive one, and maybe I can convince some great clojure devs to help me out.

What do you get out of it?

A single namespace for (almost) everything: (:require [coast.core :as coast])

Pretty good postgres support

Conventions, not configuration (this sounds familiar)

HTML helper functions (link-to, form-for… and that’s it for now 😐)

A beautiful majestic monolith ⎍

What don’t you get?

Microservices (never)

Background Jobs (yet)

Datomic/other data stores (yet)

Clojurescript (maybe)

Javascript frameworks (never)

Spec (yet?)

Some kind of integrated testing (or something) (yet)

What does a basic example look like?

You can try it without the lein template if you want, just do this from your terminal:

mkdir simple

cd simple

touch deps.edn

echo '{:deps {coast {:mvn/version "0.6.1"}}}' >> deps.edn

mkdir src

touch src/simple.clj

Then fill in simple.clj with the code below

It’s just clojure functions and maps built on top of existing libraries! That’s right, coast is not only a clever homage to rails, it’s also me literally coasting on the hard work of others! There’s a whole long list of libraries made and maintained by super talented developers that coast uses and gives credit to in the README.

Run the above code with clj -m simple and now you’re ready for WORLD DOMINATION. Compete with google time? ✅ ✅

What happens if you start a fresh lein new coast your-proj project? Now this is where the stack gets really full.

├── Procfile

├── README.md

├── profiles.clj

├── project.clj

├── resources

│ └── public

│ ├── css

│ │ └── app.css

│ └── js

│ └── app.js

├── src

│ └── your_proj

│ ├── components.clj

│ ├── controllers

│ │ ├── errors_controller.clj

│ │ └── home_controller.clj

│ ├── core.clj

│ ├── routes.clj

│ └── views

│ ├── errors.clj

│ └── home.clj

├── target

│ └── classes

└── test

└── your_proj

└── core_test.clj

The default template is chock full of good stuff. I’m going to start with the aliases which should probably live somewhere else but for now they’re in your project. Deal with it 🕶.

Postgres Database Aliases

lein db/create

lein db/drop

lein db/migration

lein db/migrate

lein db/rollback

You can probably guess what’s going to happen when you navigate to your newly created project directory and type these things in. lein db/create is going to create a new postgres database with a name like your_proj_dev . lein db/drop will drop this database. lein db/migrate runs all migrations that haven’t run yet, lein db/rollback rolls a migration back one at a time. lein db/migration create-posts will create a new migration file in resources/migrations/{timestamp}_create_posts.edn like this:

-- up

create table posts (id serial primary key, title varchar(255) not null, body text not null, created_at timestamp without time zone default (now() at time zone 'utc') ); -- down

drop table posts;

So the goal is to follow rails conventions, plural names for tables, id column for primary key, created_at timestamp.

So far things are pretty straightforward, and that’s boring so this is where things get kind of weird and start to diverge from rails quite a bit. There is no ORM here, only a VRM, so the models happen in two parts across two files.

MVC: Models

There are no objects, no methods, and no dynamic sql generation (kind of), only static. Which means you have to write sql yourself. OH NO! But it’s ok, coast has your back.

lein sql/gen posts

This command takes the name of a table and generates sql in resources/sql/posts.sql . Which looks like this

-- name: all

select *

from posts

order by created_at desc



-- name: find-by-id

-- fn: first

select *

from posts

where id = :id



-- name: where

-- fn: first

where id = :id

returning *

This SQL is just sitting here, doing nothing. Now it’s time to generate the “model” which is just a file with regular clojure functions.

lein model/gen posts

Which will create a new file: src/my_proj/models/posts.clj :

Yay functions for the database!

That’s pretty much all there is to the models, I would like to add some spec-driven validation, but I haven’t done that yet, so that stinks.

MVC: Controllers

Controllers are just like they are in other full stack frameworks, functions that glue your models and your views together.

lein controller/gen posts

Gives you this:

It’s all regular forms POSTing form data to the server and then redirecting. Just like pappy used to do! I might try to make a generator for RESTful or maybe GraphQL endpoints in the future, but for now, things are just going to have to be fast and simple.

MVC: Views

Usually I just pass the whole request map into the view functions, which kind of kills separation of concerns, but I’m ok with that for some reason. Who’s concerns are they anyway, am I right? Here’s what a generated lein view/gen posts view file looks like:

Pretty basic 💁‍♀️

In the end

This is a good starting point for something like a “full stack” clojure framework. Definitely not as robust or simple as something like luminus, but it might just be easier mostly because it’s early days and there’s no clojurescript (or javascript framework, which is a huge plus) and just one supported database… there’s a lot to do still. I don’t have a grand plan for this thing other than I want there to be multiple options when considering clojure web development, not just luminus, libraries or GTFO 👋. Hopefully coast on clojure won’t be the simplest option, but it might just be the easiest option.

I’ve already made a few sites with this framework, of course none of them have had any traction yet 😢 (that’s classic me), but it works for the most part. Give it a shot!