An opinionated starter app for full-stack web applications in Clojure
Switch branches/tags
Nothing to show
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Failed to load latest commit information.
resources
src
test/clj/app/test
.gitattributes
.gitignore
.travis.yml
LICENSE
README.md
boot.properties
build.boot

README.md

clojurice

Sponsored license Build Status

An opinionated starter app for full-stack web applications in Clojure

Requirements

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.

Instructions

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

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.

Development notes

Backend

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.

Frontend

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:

  1. Create your view under the app.views namespace, ie. app.views.foo in cljs/app/views/foo.cljs
  2. Require the 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.
  3. Require the new namespace in index.cljs.
  4. Add a route to the routes in 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.

Common namespaces

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.

Dev tools

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.

Hot Reloading

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.

Testing

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

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.