https://github.com/jarcane/clojurice.git
git clone 'https://github.com/jarcane/clojurice.git'
(ql:quickload :jarcane.clojurice)
An opinionated starter app for full-stack web applications in Clojure
You will need Boot installed, as well as Java 1.8+, and PostgreSQL 9.6+.
The default configuration expects a running PGSQL server with user/password “postgres” containing databases called “app” and “apptest” (for the integration tests), though these settings can be re-configured. See below.
The best way to start a new project is to either clone the repository, or, if you wish to start from a clean slate with git, download the master .zip, extract it locally, and git init
from there.
To start the dev environment do:
boot dev
This will start both backend and frontend compilation, with the site hosted on localhost:7000. API documentation can also be found at http://localhost:7000/api-docs
To build the project to an uberjar do:
boot build <target-dir>
An uberjar called “app-(version)-standalone.jar” will be found in the target directory. The project version number can be set in build.boot
.
Configuration is handled via EDN files under the resources/config
directory. base.edn
provides the base configuration that applies to all environments, while the two profiles, dev.edn
and prod.edn
are loaded in their respective environments and take precedence over base.edn
. In addition, on load time, the config files are checked against the Config
schema located in domain.cljc
.
Frontend configuration is provided via the API at GET /api/config
, and provides a subset of the configuration as defined in the FrontendConfig
schema in domain.cljc
.
The main backend API can be found in api.clj
and is written in compojure-api. There is also a tutorial on working with compojure-api syntax.
Dependency injection and system component handling is handled via system and the Raamwerk model. This is what enables live reloading of the backend, but also orchestrates all the components of the app (static and API servers, config, DB, etc.). The main constructors for these are found in app.systems
. There is a base build-system
function which takes a config profile name and produces the base system map for that profile, and then functions that produce the prod and dev systems.
Of most important note is the :site-endpoint
, which is the component that handles static routes like the main index and points to app.routes/site
, and :api-endpoint
, which is the component for the REST API, and points to app.api/api-routes
. Each of these functions takes a single argument (called sys
by convention), which is a subset of the system map, containing the keys listed as dependencies in the vector passed to component/using
. So in order for a component to be available to the end-point, its key needs to be added to this vector.
Database migrations are handled with a custom Flyway component, configured to automatically run on server start or reload. Migrations are located in resources/db/migrations
, which contains .sql
files for migrations, named according to the scheme described in the Flyway documentation. The Flyway
object is available from the system-map as :flyway
and can thus be called from the REPL or from any component that inherits it as a dependency. This is useful for rolling back migrations in test, etc.
For SQL abstraction, honeysql is used on top of clojure.java.jdbc. HoneySQL provides a way of writing SQL queries as maps, which can thus be built and composed as any other Clojure map, and then formatted into SQL to call with the JDBC driver. A helper function, app.query/make-query
is provided in query.sql
for wrapping the call to the JDBC driver, so one need only provide the system map and the SQL query map to get a result.
The frontend is built with reagent, using a combination of multimethod dispatch for rendering each view, and client-side routing with bide. As such, adding a new sub-view requires a few steps that are important to remember:
app.views
namespace, ie. app.views.foo
in cljs/app/views/foo.cljs
app.views.dispatch/dispatch-view
multimethod, and create your own multimethod to dispatch from a suitable key, ie. :app.foo
. The method should take two arguments, the first is the key itself, the second is any parameters from the URI.index.cljs
.router.cljs
.Main app state is kept in a shared reagent atom at app.state/app-state
. A app.api
namespace is also provided for common API calls.
An important note regarding routing: when linking to another component within the app, it is best to use the app.router/app-link
function as this hooks into the routing system. Normal hrefs will work, but force a page reload, which will be slower and also reset app-state.
In addition to the frontend and backend, there are also included some common namespaces via .cljc
files in src/cljc/app
, that allow for key data and functions to be shared by front and back. The most important of these is app.domain
in src/cljc/app/domain.cljc
. This provides the common data schemas for the application, including the schema for the configuration files.
The default configuration will open nREPL connections to both frontend at port 6809, and backend at port 6502.
The frontend environment wraps cljs-devtools for a more pleasant browser environment for Chrome. There is also an additional reagent-dev-tools element added to the page in dev mode that provides reflection to the current app state. You will want to turn on custom formatters in the Chrome Devtools for the cljs-devtools formatters to work.
A boot cljfmt
task is provided which will run cljfmt on all files in the src directory. The check
and fix
tasks from boot-cljfmt are also available directly, and can be used to run against individual files or directories as needed.
For basic linting a boot analyse
task is provided, which will check the source files with kibit and bikeshed for common style issues.
Both frontend and backend code have been configured to automatically reload on file changes. There's even a helpful audio cue to notify you once a rebuild is done.
Note that the full backend server system will only be restarted completely when certain files change. This is configured through the build.boot
dev task with the :files
parameter to the system
step.
Some basic integration tests have been provided. You can run these with boot test
, or with boot test-watch
, which will start a watcher and run all tests on file change.
The tests include browser testing via etaoin, and you will also need to install the Chrome webdriver. Information and links on how to do this can be found here. On Mac it can be installed with brew install chromedriver
, or on Windows with scoop install chromedriver
. You will also of course need Chrome.
This app is based originally on system-template with some further guidance from tenzing.
Developed by Annaia Berry (@jarcane). Development made possible by Futurice.
Copyright (C) 2018 Annaia Berry. The code is distributed under the Eclipse Public License v2.0 or any later version. For more information see LICENSE
in the root directory.