The easy full stack clojure web framework
Current version: [coast "0.6.9"]
Create a new coast project like this
lein new coast blog
cd blog
Let's set up the database!
lein db/create # assumes a running postgres server. creates a new db called blog_dev
Let's create a table to store posts and generate some code to so we can interact with that table!
lein db/migration create-posts title:text body:text
lein db/migrate
lein mvc/gen posts
Go ahead and add the routes too
(ns blog.routes
(:require [coast.core :as coast]
[blog.controllers.home-controller :as home]
[blog.controllers.errors-controller :as errors]
[blog.controllers.posts-controller :as posts]))
(def routes
(-> (coast/get "/" home/index)
(coast/resource :posts)
(coast/route-not-found errors/not-found)))
Let's see our masterpiece so far
lein repl ; or start a repl your preferred way
(coast) ; => Listening on port 1337
You should be greeted with the text "You're coasting on clojure!" when you visit http://localhost:1337 and when you visit http://localhost:1337/posts you should be able to add, edit, view and delete the rows from the post table!
Amazing!
The only currently supported database is postgres. PRs gladly accepted to add more, the db abstraction is jdbc along with a sql library I wrote oksql.
There are a few generators in the form of lein aliases to help you get the boilerplate-y database stuff out of the way:
lein db/create
lein db/drop
lein db/migration
lein db/migrate
Those all pretty much do what you think they do.
lein db/create
The database name is your project name underscore dev. So
if your project name is cryptokitties
, your db would be named cryptokitties_dev
. This assumes a running
postgres server and the process running leiningen has permission to create databases. I don't know what happens
when those requirements aren't met. Probably an error of some kind. You can actually change this from project.clj
since the aliases have the name in them as the first argument.
lein db/drop
The opposite of db/create
. Again, I don't know what happens when you run drop before create. Probably an error.
lein db/migration
This creates a migration which is just plain sql 😎 with a timestamp and the filename in the resources/migrations
folder
lein db/migration the-name-of-a-migration
This creates an empty migration that looks like this
-- up
-- down
lein db/migration create-posts
This creates a migration that creates a new table with the "default coast" columns of id and created_at.
-- up
create table posts (
id serial primary key,
created_at timestamp without time zone default (now() at time zone 'utc')
)
-- down
drop table posts
lein db/migration create-posts title:text body:text
This makes a new migration that creates a table with the given name:type columns. Juicy.
-- up
create table posts (
id serial primary key,
title text,
body text,
created_at timestamp without time zone default (now() at time zone 'utc')
)
-- down
drop table posts
lein db/migrate
This performs the migration, if one of them fails, it should stop migrating the rest.
Models are just clojure functions that call external sql files in resources/sql
There's a generator for the basic crud operations:
lein model/gen posts
This requires that the posts table already exists and it creates two files:
resources/sql/posts.sql
src/models/posts.clj
The model file and the sql file work together for sql queries and the where clause on updates and deletes. Here's an example of how they work together.
Here's posts.sql
. Each query is separated by a newline and -- name
.
They all have a -- name
which comes after the colon. These values can be any string that can be resolved to a clojure function.
They also optionally have a function which runs after the results are received from the database, and this functions operates on all rows
at once, not just row by row, luckily, clojure still works and you can use map
for row by row function application.
-- name: all
select *
from posts
order by created_at desc
-- name: find-by-id
-- fn: first
select *
from posts
where id = :id
-- name: where
where id = :id
returning *
Here is where clojure comes in and references the posts.sql
file
(ns blog.models.posts
(:require [coast.db :as db])
(:refer-clojure :exclude [update]))
(def columns [:title :body])
; the keyword namespace is the file where the query is located, and the name corresponds to -- name: all
(defn all []
(db/query :posts/all))
; same thing here
(defn find-by-id [id]
(db/query :posts/find-by-id {:id id}))
; insert does not reference the sql file at all, it dynamically generates the sql
(defn insert [m]
(->> (select-keys m columns)
(db/insert :posts)))
; db/update and db/delete both reference the :file/where name which can be anything or you can have multiple
; queries with multiple where's if you want to
(defn update [id m]
(as-> (select-keys m columns) %
(db/update :posts % :posts/where {:id id})))
; same thing here, it's :table :file/name where name is where in this case
; could easily be
; (db/delete :posts :posts/delete-where {:id id}) or anything you can imagine
(defn delete [id]
(db/delete :posts :posts/where {:id id}))
In my short web programming career, I've found two things that I really like, clojure and rails. This is my attempt to put the two together.
This framework is only possible because of the hard work of a ton of great clojure devs who graciously open sourced their projects that took a metric ton of hard work. Here's the list of open source projects that coast uses:
Can you improve this documentation?Edit on GitHub
cljdoc is a website building & hosting documentation for Clojure/Script libraries
× close