Sometimes you want a pure Clojure server with a React web client. It is best if you create a new one instead of using other people’s templates, because you can also get the newest libraries, frameworks, and security fixes at the same time. If you are determined to get going now with a Clojure / React JS server, grab the latest copy from https://github.com/TGeneDavis/clojure-server-with-reactjs-template on GitHub.
Creating the Clojure Server
We’re going to start with a basic Luminus Framework Clojure server. We’ll add http-kit for simple http protocol handling, H2 for a quick and easy database, swagger for RESTful API documenting and Buddy with middleware authentication.
1 |
lein new luminus myserver +http-kit +h2 +swagger +auth |
Configure the Server Port
React JS and Clojure’s Luminus Framework both use port 3000 during development. This will cause you headaches. Change the development port of the Clojure server to 3001. You will find the configuration in the ‘dev-config.edn’ file.
Database Migrations and Rollbacks
To run database migrations and rollbacks, use the commands “lein run migrate” and “lein run rollback”. You’ll find the currently written migrations in the “resources/migrations” folder in the project.
To create a new migration, start by connecting to the running server with a remote repl. Start the server with “lein run”. Then in another terminal, connect using the following command.
1 |
lein repl :connect localhost:7000 |
You are now in the ‘User’ namespace. The function, “create-migration” is in the user namespace, so use the following command to create a new up and down migration for migrating and rolling back database changes.
1 |
(create-migration "my-new-migration") |
Now you have a new migration with a timestamp to ensure migration order and uniqueness.
Interacting with SQL from Clojure
SQL for interacting with the database is found in “resources/sql/queries”. An example of using the SQL you create is in the core_test.clj file at “test/clj/myserver/db/core_test.clj”. Also, see the documentation for “next.jdbc”.
Swagger and RESTful APIs
Swagger and RESTful APIs are extremely important for integrating a React client. When your server is running, you’ll find the swagger documentation page at “/api/api-docs/index.html”.
1 |
http://localhost:3001/api/api-docs/index.html |
You start with some simple API endpoints to play with. The sample APIs include ping, plus, and upload/download.
If your React client needs to pass data to and from the server, you’ll set up fetch calls to access the server’s APIs. The swagger page will be invaluable in checking that your server code is running correctly when you don’t see the results you want from the React client.
Adding a React Client to the Clojure Server
Now we’ll add a React client to the Clojure server. The trick here is that we add one or more React projects as sub-projects to the Clojure server. Then we set up a deployment script in the React project that builds and deploys the current React client to a “resources/<some project directory>” directory. The public directory handles serving static resources, which includes compiled React projects.
Note that the HTML directory in the resources directory is for Selmer templates. Those are all dynamic and very handy if you want to include templating, such as a dynamic config.json file for passing server configurations to you React clients when they are loaded.
Add a sub-directory to your ‘resources’ directory. For this example, create the directory “resources/publicmyclient”.
Now, create the react app’s sub-project in the Clojure server’s project. From the root of the Clojure project, issue the following command.
1 |
npx create-react-app myclient |
It’s likely you’ll want the React Router installed, too. From inside your new ‘myclient’ sub-project, use the following command.
1 |
npm install react-router-dom@6 |
Next use the instructions over at the React Router website. I’ll duplicate them here, for convenience.
Make your index.js look like this.
1 2 3 4 5 6 7 8 9 10 11 12 |
import * as React from "react"; import * as ReactDOM from "react-dom"; import { HashRouter } from "react-router-dom"; import "./index.css"; import App from "./App"; ReactDOM.render( <HashRouter> <App /> </HashRouter>, document.getElementById("root") ); |
Open App.js and copy in this code.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
import * as React from "react"; import { Routes, Route, Link } from "react-router-dom"; import "./App.css"; // App.js function Home() { return ( <> <main> <h2>Welcome to the homepage!</h2> <p>You can do this, I believe in you.</p> </main> <nav> <Link to="/about">About</Link> </nav> </> ); } function About() { return ( <> <main> <h2>Who are we?</h2> <p> That feels like an existential question, don't you think? </p> </main> <nav> <Link to="/">Home</Link> </nav> </> ); } function App() { return ( <div className="App"> <h1>Welcome to React Router!</h1> <Routes> <Route path="/" element={<Home />} /> <Route path="/about" element={<About />} /> </Routes> </div> ); } export default App; |
If you use ‘npm start’ from inside your client directory, then you can test out your new react client, including navigating between your two pages.
You’re not ready to run serve it from the Clojure server, yet, but we’re close. From inside the new React client, you can use the standard commands, such as ‘npm install’, ‘npm run build’ and ‘npm start’. Next we need to configure the React JS app to run from a subdirectory and add a ‘npm run deploy’ command.
Configuring React to Run From a Sub-directory
Add the following line to package.json in the myclient subproject.
1 |
"homepage": "/myclient", |
I usually add it right under the “version”, somewhere around line 4.
Notice the use of HashRouter in index.js. This makes things work a little more smoothly when using subdirectories.
Configure a ‘deploy’ Task
Replace the line with the eject script in package.json with the following line.
1 |
"deploy": "react-scripts build && rm -rf ../resources/publicmyclient && cp -R build ../resources/publicmyclient" |
Use the following commands from the myclient subdirectory to build and deploy the ‘myclient’ react app to the Clojure server’s public directory.
1 |
npm install |
1 |
npm run build |
1 |
npm run deploy |
Handle Routing to the Correct Resource
Finally, we need to set up the Clojure server to find the new React app in the ‘publicmyclient’ folder in the ‘resources’ directory.
Re-write the handler.clj file to read as follows.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
(ns myserver.handler (:require [myserver.middleware :as middleware] [myserver.layout :refer [error-page]] [myserver.routes.home :refer [home-routes]] [myserver.routes.services :refer [service-routes]] [reitit.swagger-ui :as swagger-ui] [reitit.ring :as ring] [ring.middleware.content-type :refer [wrap-content-type]] [ring.middleware.webjars :refer [wrap-webjars]] [myserver.env :refer [defaults]] [mount.core :as mount])) (mount/defstate init-app :start ((or (:init defaults) (fn []))) :stop ((or (:stop defaults) (fn [])))) (mount/defstate app-routes :start (ring/ring-handler (ring/router [(home-routes) (service-routes)]) (ring/routes (ring/create-resource-handler {:path "/myclient" :root "publicmyclient"}) (swagger-ui/create-swagger-ui-handler {:path "/swagger-ui" :url "/api/swagger.json" :config {:validator-url nil}}) (ring/create-resource-handler {:path "/" :root "public"}) (wrap-content-type (wrap-webjars (constantly nil))) (ring/create-default-handler {:not-found (constantly (error-page {:status 404, :title "404 - Page not found"})) :method-not-allowed (constantly (error-page {:status 405, :title "405 - Not allowed"})) :not-acceptable (constantly (error-page {:status 406, :title "406 - Not acceptable"}))})))) (defn app [] (middleware/wrap-base #'app-routes)) |
Running the Clojure Server
That’s it. We’re ready to run a Clojure server with a React app.
Then from the Clojure server’s root directory, use the following command to run the Clojure server.
1 |
lein run |
Now use a browser to see the React client in the myclient subdirectory.
1 |
http://localhost:3001/myclient |
This brings up the React web client.
Don’t forget, you still have access to the Selmer templates at ‘http://localhost:3001’.
Also, the Swagger API documentation is at ‘http://localhost:3001/api/api-docs/index.html’.