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:
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
Configuration is handled via EDN files under the
base.edn provides the base configuration that applies to all environments, while the two profiles,
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
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
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
: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:
- Create your view under the
- Require the
app.views.dispatch/dispatch-viewmultimethod, 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.
- Require the new namespace in
- Add a route to the routes in
Main app state is kept in a shared reagent atom at
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
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.
boot cljfmt task is provided which will run cljfmt on all files in the src directory. The
fix tasks from boot-cljfmt are also available directly, and can be used to run against individual files or directories as needed.
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
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.
Credits and License
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.