├── .cspell └── practicalli-custom-dictionary.txt ├── .gitattributes ├── .github ├── CODEOWNERS ├── config │ ├── gitleaks.toml │ ├── lychee.toml │ ├── markdown-link-check.json │ ├── markdown-lint.jsonc │ ├── megalinter.yaml │ └── secretlintrc.json ├── pull_request_template.md └── workflows │ ├── changelog-check.yaml │ ├── megalinter.yaml │ ├── publish-book.yaml │ └── scheduled-version-check.yaml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── Makefile ├── README.md ├── additional-resources.md ├── content-plan.md ├── docs ├── .nojekyll ├── adding-more-route │ └── using-cond-function.md ├── app-servers │ ├── app-server-logging.md │ ├── atom-based-restart.md │ ├── clojure-project.md │ ├── create-server.md │ ├── debugging.md │ ├── http-kit-server-options.md │ ├── index.md │ ├── java-system-properties.md │ ├── jetty-server-options.md │ ├── middleware.md │ ├── overview.md │ ├── route-requests.md │ ├── routing-libraries.md │ ├── routing.md │ ├── set-listen-port.md │ ├── simple-restart.md │ ├── start-server.md │ └── static-content.md ├── assets │ ├── android-chrome-192x192.png │ ├── android-chrome-512x512.png │ ├── apple-touch-icon.png │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── favicon.ico │ ├── favicon.svg │ ├── images │ │ ├── practicalli-logo.png │ │ └── social │ │ │ └── README.md │ ├── practicalli-logo.svg │ └── stylesheets │ │ └── extra.css ├── building-api │ ├── cheshire.md │ ├── compojure-api-template.md │ ├── create-compojure-api-project.md │ ├── end-to-end-testing │ │ ├── curl.md │ │ ├── httpie.md │ │ ├── index.md │ │ ├── postman.md │ │ └── swagger.md │ ├── index.md │ ├── json-files.md │ ├── plumatic-schema.md │ ├── projects │ │ ├── game-scoreboard-ui │ │ │ ├── create-project.md │ │ │ └── index.md │ │ └── game-scoreboard │ │ │ ├── defining-scoreboard.md │ │ │ ├── defining-scores.md │ │ │ └── index.md │ ├── reitit │ │ └── index.md │ ├── ring-mock.md │ ├── ring-swagger.md │ ├── swagger.md │ ├── terminology.md │ └── testing-api.md ├── clojure-databases │ ├── crux │ │ └── index.md │ └── index.md ├── full-app │ └── index.md ├── index.md ├── introduction │ ├── contributing.md │ ├── overview.md │ ├── repl-workflow.md │ ├── requirements.md │ └── writing-tips.md ├── libraries │ └── reitit │ │ ├── constructing-routes.md │ │ └── index.md ├── micro-framework │ ├── edge │ │ └── index.md │ ├── index.md │ ├── luminus │ │ └── index.md │ └── pedestal │ │ └── index.md ├── micro-services │ └── index.md ├── project-url-shortner │ ├── add-alias-to-database.md │ ├── add-static-resources.md │ ├── alias-generator.md │ ├── compojure-template.md │ ├── create-database.md │ ├── create-html-form.md │ ├── create-project.md │ ├── delete-alias-from-database.md │ ├── design-data-structure.md │ ├── disable-anti-forgery-check.md │ ├── get-alias-from-database.md │ ├── html-form.md │ ├── if-let-function.md │ ├── index.md │ ├── named-alias-handler.md │ ├── persist-aliases.md │ ├── postgres-setup.md │ ├── redirect-to-full-url.md │ ├── redis-setup.md │ ├── refacor-hiccup-form.md │ ├── return-short-url.md │ ├── return-url-aliases.md │ ├── run-project.md │ ├── test-app-reloading.md │ ├── using-ring-redirect.md │ └── whats-in-a-request.md ├── projects │ ├── banking-on-clojure │ │ ├── account-overview-page.md │ │ ├── clojure-server-project.md │ │ ├── clojure-spec-generate-mock-data.md │ │ ├── continuous-integration.md │ │ ├── create-records.md │ │ ├── cyclic-load-dependency.md │ │ ├── database-queries.md │ │ ├── database-tables.md │ │ ├── delete-records.md │ │ ├── deployment-pipeline.md │ │ ├── deployment-via-ci.md │ │ ├── development-database.md │ │ ├── generate-data-from-specs.md │ │ ├── honeysql.md │ │ ├── index.md │ │ ├── instrument-next-jdbc-functions.md │ │ ├── namespace-design.md │ │ ├── production-database.md │ │ ├── read-records.md │ │ ├── refactor-handler.md │ │ ├── spec-generative-testing.md │ │ ├── ui-handler-functions.md │ │ ├── unit-testing-the-database.md │ │ ├── unit-tests.md │ │ └── update-records.md │ ├── game-scoreboard-api │ │ └── index.md │ ├── index.md │ ├── leiningen │ │ ├── todo-app │ │ │ ├── compojure │ │ │ │ ├── .md │ │ │ │ ├── about.md │ │ │ │ ├── adding-dependency.md │ │ │ │ ├── adding-goodbye-route.md │ │ │ │ ├── code-so-far.md │ │ │ │ ├── defroutes.md │ │ │ │ ├── index.md │ │ │ │ ├── lisp-calculator.md │ │ │ │ ├── show-request-info.md │ │ │ │ ├── theory-local-name-bindings.md │ │ │ │ ├── theory-routing.md │ │ │ │ ├── theory-using-hash-maps.md │ │ │ │ ├── using-compojure.md │ │ │ │ └── variable-path-elements.md │ │ │ ├── connect-to-postgres │ │ │ │ ├── add-database-dependencies.md │ │ │ │ ├── define-db-connection.md │ │ │ │ └── index.md │ │ │ ├── create-a-handler-function │ │ │ │ ├── add-not-found.md │ │ │ │ ├── code-so-far.md │ │ │ │ ├── if-function.md │ │ │ │ ├── index.md │ │ │ │ └── maps-and-keywords.md │ │ │ ├── create-a-project │ │ │ │ ├── code-so-far.md │ │ │ │ ├── index.md │ │ │ │ └── update-project-details.md │ │ │ ├── create-a-webserver-with-ring │ │ │ │ ├── add-a-jetty-webserver.md │ │ │ │ ├── add-ring-dependency.md │ │ │ │ ├── code-so-far.md │ │ │ │ ├── coersing-types-and-java-lang.md │ │ │ │ ├── configure-main-namespace.md │ │ │ │ ├── include-ring-library.md │ │ │ │ ├── index.md │ │ │ │ ├── namespaces.md │ │ │ │ └── run-webserver.md │ │ │ ├── database-model │ │ │ │ ├── alternative-approaches.md │ │ │ │ ├── create-table.md │ │ │ │ ├── create-task.md │ │ │ │ ├── delete-task.md │ │ │ │ ├── index.md │ │ │ │ └── show-all-task.md │ │ │ ├── heroku │ │ │ │ ├── code-so-far.md │ │ │ │ ├── deploy.md │ │ │ │ ├── index.md │ │ │ │ ├── procfile.md │ │ │ │ └── update-project.md │ │ │ ├── hiccup │ │ │ │ ├── code-so-far.md │ │ │ │ ├── create-new-handler.md │ │ │ │ ├── index.md │ │ │ │ └── updating-handlers-with-hiccup.md │ │ │ ├── index.md │ │ │ ├── introducing-ring │ │ │ │ └── index.md │ │ │ ├── postgres │ │ │ │ ├── connect-to-heroku-postgres-from-clients.md │ │ │ │ ├── dataclips.md │ │ │ │ ├── environment-variables.md │ │ │ │ ├── index.md │ │ │ │ ├── install.md │ │ │ │ ├── jira-ticket.md │ │ │ │ ├── lobo-table-creation.md │ │ │ │ ├── pg-admin.md │ │ │ │ ├── postgres-cli.md │ │ │ │ ├── postgres-commands.md │ │ │ │ ├── postgres-performance-analytics.md │ │ │ │ └── postgres-toolbelt-commands.md │ │ │ ├── refactor-namespace │ │ │ │ ├── base-routes.md │ │ │ │ ├── code-so-far.md │ │ │ │ ├── core.md │ │ │ │ ├── index.md │ │ │ │ ├── play-routes.md │ │ │ │ └── task-routes.md │ │ │ ├── reloading-the-application │ │ │ │ ├── code-so-far.md │ │ │ │ ├── index.md │ │ │ │ ├── middleware.md │ │ │ │ └── test-your-code-reloads.md │ │ │ ├── task-handlers │ │ │ │ ├── add-a-task.md │ │ │ │ ├── delete-a-task.md │ │ │ │ ├── index.md │ │ │ │ └── show-task.md │ │ │ └── unit-test-handler-function │ │ │ │ └── index.md │ │ └── working-example │ │ │ └── index.md │ ├── slack-app │ │ ├── create-slack-app.md │ │ ├── index.md │ │ ├── slack-api-methods.md │ │ └── slack-scopes.md │ ├── status-monitor-deps │ │ ├── application-server.md │ │ ├── continuous-integration.md │ │ ├── debugging-requests.md │ │ ├── deployment-via-ci.md │ │ ├── index.md │ │ ├── refactor-handlers-and-tests.md │ │ └── unit-test-mocking-handlers.md │ └── todo-tracker │ │ └── index.md ├── reference │ ├── continuous-integration │ │ └── heroku │ │ │ └── index.md │ ├── index.md │ └── ring │ │ ├── index.md │ │ └── request-map.md ├── relational-databases-and-sql │ ├── h2-database.md │ ├── h2-database │ │ ├── database-tools.md │ │ ├── index.md │ │ └── schema-design.md │ ├── index.md │ ├── managing-connections.md │ ├── next-jdbc-library │ │ ├── add-to-project.md │ │ ├── connection-pool-lifecycle.md │ │ ├── database-specifications.md │ │ ├── index.md │ │ ├── next-jdbc-and-resultsets.md │ │ └── simple-example.md │ └── postgresql-database.md └── service-repl-workflow │ ├── aero.md │ ├── donut-system.md │ ├── index.md │ ├── integrant │ ├── index.md │ ├── integrant-system.md │ └── repl.md │ ├── mulog-events.md │ ├── portal.md │ └── system-repl.md ├── mkdocs.yml ├── overrides ├── 404.html ├── main.html └── partials │ ├── header.html │ ├── palette.html │ └── source.html ├── wip-integrant.md └── work-in-progress.md /.cspell/practicalli-custom-dictionary.txt: -------------------------------------------------------------------------------- 1 | Babashka 2 | Bidi 3 | Bulma 4 | CLASSPATH 5 | CQRS 6 | Classpath 7 | Clojars 8 | Clojurians 9 | Clojurists 10 | Coersing 11 | Cognitect 12 | Compojure 13 | DATABSAE 14 | Dataclips 15 | Datafy 16 | Datalog 17 | Datomic 18 | Dpropertyname 19 | Elastisch 20 | Enilve 21 | Gitub 22 | HSQLDB 23 | Hikari 24 | Httpkit 25 | Instarepl 26 | JAAS 27 | JDBC 28 | JNDI 29 | JSESSIONID 30 | JUXT 31 | Jetpack 32 | Juxt 33 | Jython 34 | Kanban 35 | Kaocha 36 | LICENCE 37 | Leaderboard 38 | Leiningen 39 | Licence 40 | Lighttable 41 | Luminus 42 | MVCC 43 | Malli 44 | Migratus 45 | Mortgate 46 | NOCREATEDB 47 | NOCREATEROLE 48 | NOSUPERUSER 49 | OSSP 50 | Organisation 51 | Organising 52 | PGPASSWORD 53 | PGSQL 54 | PGUSER 55 | Plumatic 56 | Posgres 57 | Posgresql 58 | Practicalli 59 | Procfile 60 | Reitit 61 | Rocketpack 62 | SIGTERM 63 | Selmer 64 | Servlet 65 | Shortner 66 | Spacemacs 67 | Spath 68 | TIMESTAMPTZ 69 | Tibco 70 | Toolbelt 71 | Uberjar 72 | VARCHAR 73 | Webapps 74 | XTDB 75 | Yada 76 | Zulip 77 | allowfullscreen 78 | arity 79 | authorisation 80 | behaviour 81 | bidi 82 | bulma 83 | cimg 84 | circleci 85 | classname 86 | classpath 87 | clojars 88 | cmdline 89 | compojure 90 | cond 91 | cpcache 92 | createdb 93 | customising 94 | dataclips 95 | datafy 96 | datasource 97 | dbeaver 98 | dbname 99 | dbtype 100 | defmethod 101 | defn 102 | defonce 103 | defproject 104 | defroute 105 | defroutes 106 | defschema 107 | depstar 108 | deref 109 | devel 110 | divs 111 | doseq 112 | duckduckgo 113 | dynos 114 | endtabs 115 | endyoutube 116 | expresssion 117 | favourite 118 | figwheel 119 | fontmanager 120 | getenv 121 | gitlibs 122 | graphicsenv 123 | gravitar 124 | hikari 125 | honeysql 126 | hotload 127 | hsts 128 | httpkit 129 | hypothesising 130 | iframes 131 | isalist 132 | javax 133 | jdbc 134 | joda 135 | jsonista 136 | juxt 137 | kaocha 138 | keywordize 139 | killall 140 | kondo 141 | kubernetes 142 | lambdaisland 143 | lein 144 | leiningen 145 | libfile 146 | licence 147 | luminus 148 | macroexpand 149 | maintenenace 150 | mchange 151 | metosin 152 | migratus 153 | mulog 154 | muuntaja 155 | namepace 156 | namesapces 157 | nosniff 158 | notfound 159 | nrepl 160 | oejs 161 | openapi 162 | optimised 163 | personalised 164 | pgsql 165 | plumatic 166 | posgres 167 | postgresqltutorial 168 | pprint 169 | practicalli 170 | printerjob 171 | println 172 | procfile 173 | procpid 174 | psql 175 | readline 176 | rebl 177 | recognise 178 | refacor 179 | reitit 180 | resultset 181 | resultsets 182 | rocksdb 183 | sameorigin 184 | seancorfield 185 | servlet 186 | shortner 187 | shorturl 188 | sslfactory 189 | sslmode 190 | stuartsierra 191 | subname 192 | subprotocol 193 | tmpdir 194 | toolbelt 195 | uberjar 196 | unstrument 197 | varchar 198 | weavejester 199 | webapps 200 | webdev 201 | webp 202 | webservice 203 | websockets 204 | yada 205 | yugabyte 206 | zaxxer 207 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Configure Languages for GitHub repository using Linguist 2 | 3 | # Markdown & Make detection, 4 | # exclude HTML, CSS & JavaScript 5 | docs/** linguist-detectable 6 | *.md linguist-detectable=true 7 | make linguist-detectable=true 8 | *.css linguist-detectable=false 9 | *.js linguist-detectable=false 10 | *.html linguist-detectable=false 11 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Codeowners 2 | 3 | # Default owner accounts for the current repository 4 | # Automatically added as a reviewr to all pull requests (not including drafts) 5 | 6 | * @practicalli-johnny 7 | -------------------------------------------------------------------------------- /.github/config/gitleaks.toml: -------------------------------------------------------------------------------- 1 | title = "gitleaks config" 2 | 3 | [allowlist] 4 | description = "global allow lists" 5 | paths = [ 6 | '''gitleaks.toml''', 7 | '''(.*?)(jpg|gif|doc|docx|zip|xls|pdf|bin|svg|socket)$''', 8 | '''(go.mod|go.sum)$''', 9 | '''gradle.lockfile''', 10 | '''node_modules''', 11 | '''package-lock.json''', 12 | '''pnpm-lock.yaml''', 13 | '''Database.refactorlog''', 14 | '''vendor''', 15 | ] 16 | 17 | [[rules]] 18 | description = "AWS Example API Key" 19 | id = "aws-example-api-key" 20 | regex = '''AKIAIOSFODNN7EXAMPLE''' 21 | keywords = [ 22 | "awstoken", 23 | ] 24 | -------------------------------------------------------------------------------- /.github/config/lychee.toml: -------------------------------------------------------------------------------- 1 | 2 | # ---------------------------------------- 3 | # Base URL or website root directory to check relative URLs. 4 | base = "https://practical.li/clojure-web-services" 5 | 6 | # Only test links with the given schemes (e.g. https). 7 | # Omit to check links with any other scheme. 8 | # At the moment, we support http, https, file, and mailto. 9 | scheme = ["https"] 10 | 11 | # ---------------------------------------- 12 | # Exclusions 13 | 14 | # Exclude URLs and mail addresses from checking (supports regex). 15 | exclude = ['^https://127.0.0.0', '^https://www\.linkedin\.com', '^https://web\.archive\.org/web/'] 16 | 17 | # Exclude these filesystem paths from getting checked. 18 | exclude_path = ["mkdocs.yml", "overrides", "includes", ".github", ".git"] 19 | 20 | # Exclude all private IPs from checking. 21 | # Equivalent to setting `exclude_private`, `exclude_link_local`, and 22 | # `exclude_loopback` to true. 23 | exclude_all_private = true 24 | 25 | # Check mail addresses 26 | include_mail = false 27 | # ---------------------------------------- 28 | -------------------------------------------------------------------------------- /.github/config/markdown-link-check.json: -------------------------------------------------------------------------------- 1 | { 2 | "ignorePatterns": [ 3 | { 4 | "pattern": "^http://localhost" 5 | }, 6 | { 7 | "pattern": "^mailto:*" 8 | }, 9 | { 10 | "pattern": "^#*" 11 | }, 12 | { 13 | "pattern": "^https://127.0.0.0/" 14 | } 15 | ], 16 | "timeout": "20s", 17 | "retryOn429": true, 18 | "retryCount": 5, 19 | "fallbackRetryDelay": "30s", 20 | "aliveStatusCodes": [ 21 | 200, 22 | 206 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /.github/config/secretlintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": [ 3 | { 4 | "id": "@secretlint/secretlint-rule-basicauth", 5 | "options": { 6 | "allows": [ 7 | "hostname.domain.com", 8 | "jdbc:postgresql://:port/?user=&password=", 9 | "postgres://postgres://username:password@hostname.domain.com:1234/database-name" 10 | ] 11 | } 12 | } 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | :memo: Description 2 | 3 | 4 | :white_check_mark: Checklist 5 | 6 | - [ ] Commits should be cryptographically signed (SSH or GPG) 7 | 8 | 9 | ## Practicalli Guidelines 10 | 11 | Please follow these guidelines when submitting a pull request 12 | 13 | - refer to all relevant issues, using `#` followed by the issue number (or paste full link to the issue) 14 | - PR should contain the smallest possible change 15 | - PR should contain a very specific change 16 | - PR should contain only a single commit (squash your commits locally if required) 17 | - Avoid multiple changes across multiple files (raise an issue so we can discuss) 18 | - Avoid a long list of spelling or grammar corrections. These take too long to review and cherry pick. 19 | 20 | ## Submitting articles 21 | 22 | [Create an issue using the article template](https://github.com/practicalli/blog-content/issues/new?assignees=&labels=article&template=article.md&title=Suggested+article+title), 23 | providing as much detail as possible. 24 | 25 | ## Website design 26 | 27 | Suggestions about website design changes are most welcome, especially in terms of usability and accessibility. 28 | 29 | Please raise an issue so we can discuss changes first, especially changes related to aesthetics. 30 | 31 | ## Review process 32 | 33 | All pull requests are reviewed by @practicalli-johnny and feedback provided, usually the same day but please be patient. 34 | -------------------------------------------------------------------------------- /.github/workflows/changelog-check.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # Check CHANGELOG.md file updated for every pull request 3 | 4 | name: Changelog Check 5 | on: 6 | pull_request: 7 | paths-ignore: 8 | - "README.md" 9 | types: [opened, synchronize, reopened, ready_for_review, labeled, unlabeled] 10 | 11 | jobs: 12 | changelog: 13 | name: Changelog Update Check 14 | runs-on: ubuntu-latest 15 | steps: 16 | - run: echo "🚀 Job automatically triggered by ${{ github.event_name }}" 17 | - run: echo "🐧 Job running on ${{ runner.os }} server" 18 | - run: echo "🐙 Using ${{ github.ref }} branch from ${{ github.repository }} repository" 19 | 20 | # Git Checkout 21 | - name: Checkout Code 22 | uses: actions/checkout@v4 23 | with: 24 | fetch-depth: 0 25 | sparse-checkout: | 26 | docs 27 | overrides 28 | .github 29 | CHANGELOG.md 30 | - run: echo "🐙 Sparse Checkout of ${{ github.repository }} repository to the CI runner." 31 | 32 | # Changelog Enforcer 33 | - name: Changelog Enforcer 34 | uses: dangoslen/changelog-enforcer@v3 35 | with: 36 | changeLogPath: "CHANGELOG.md" 37 | skipLabels: "skip-changelog-check" 38 | 39 | # Summary and status 40 | - run: echo "🎨 Changelog Enforcer quality checks completed" 41 | - run: echo "🍏 Job status is ${{ job.status }}." 42 | -------------------------------------------------------------------------------- /.github/workflows/megalinter.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # MegaLinter GitHub Action configuration file 3 | # More info at https://megalinter.io 4 | # All variables described in https://megalinter.io/latest/configuration/ 5 | 6 | name: MegaLinter 7 | on: 8 | workflow_dispatch: 9 | pull_request: 10 | branches: [main] 11 | push: 12 | branches: [main] 13 | 14 | # Run Linters in parallel 15 | # Cancel running job if new job is triggered 16 | concurrency: 17 | group: "${{ github.ref }}-${{ github.workflow }}" 18 | cancel-in-progress: true 19 | 20 | jobs: 21 | megalinter: 22 | name: MegaLinter 23 | runs-on: ubuntu-latest 24 | steps: 25 | - run: echo "🚀 Job automatically triggered by ${{ github.event_name }}" 26 | - run: echo "🐧 Job running on ${{ runner.os }} server" 27 | - run: echo "🐙 Using ${{ github.ref }} branch from ${{ github.repository }} repository" 28 | 29 | # Git Checkout 30 | - name: Checkout Code 31 | uses: actions/checkout@v4 32 | with: 33 | fetch-depth: 0 34 | sparse-checkout: | 35 | docs 36 | overrides 37 | .github 38 | - run: echo "🐙 Sparse Checkout of ${{ github.repository }} repository to the CI runner." 39 | 40 | # MegaLinter Configuration 41 | - name: MegaLinter Run 42 | id: ml 43 | ## latest release of major version 44 | uses: oxsecurity/megalinter/flavors/documentation@v8 45 | env: 46 | # ADD CUSTOM ENV VARIABLES OR DEFINE IN MEGALINTER_CONFIG file 47 | MEGALINTER_CONFIG: .github/config/megalinter.yaml 48 | 49 | GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" # report individual linter status 50 | # Validate all source when push on main, else just the git diff with live. 51 | VALIDATE_ALL_CODEBASE: >- 52 | ${{ github.event_name == 'push' && github.ref == 'refs/heads/main'}} 53 | 54 | # Upload MegaLinter artifacts 55 | - name: Archive production artifacts 56 | if: ${{ success() }} || ${{ failure() }} 57 | uses: actions/upload-artifact@v4 58 | with: 59 | name: MegaLinter reports 60 | path: | 61 | megalinter-reports 62 | mega-linter.log 63 | 64 | # Summary and status 65 | - run: echo "🎨 MegaLinter quality checks completed" 66 | - run: echo "🍏 Job status is ${{ job.status }}." 67 | -------------------------------------------------------------------------------- /.github/workflows/publish-book.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Publish Book 3 | on: 4 | # Manually trigger workflow 5 | workflow_dispatch: 6 | 7 | # Run work flow conditional on linter workflow success 8 | workflow_run: 9 | workflows: 10 | - "MegaLinter" 11 | paths: 12 | - "docs/**" 13 | - "includes/**" 14 | - "overrides/**" 15 | - "mkdocs.yaml" 16 | branches: 17 | - main 18 | types: 19 | - completed 20 | 21 | permissions: 22 | contents: write 23 | 24 | jobs: 25 | publish-book: 26 | name: MkDocs Publish 27 | runs-on: ubuntu-latest 28 | steps: 29 | - run: echo "🚀 Job automatically triggered by ${{ github.event_name }}" 30 | - run: echo "🐧 Job running on ${{ runner.os }} server" 31 | - run: echo "🐙 Using ${{ github.ref }} branch from ${{ github.repository }} repository" 32 | 33 | - name: Checkout Code 34 | uses: actions/checkout@v4 35 | with: 36 | fetch-depth: 0 37 | sparse-checkout: | 38 | docs 39 | includes 40 | overrides 41 | - run: echo "🐙 ${{ github.repository }} repository sparse-checkout to the CI runner." 42 | 43 | - name: Setup Python 44 | uses: actions/setup-python@v5 45 | with: 46 | python-version: 3.x 47 | 48 | - name: Cache 49 | uses: actions/cache@v4 50 | with: 51 | key: ${{ github.ref }} 52 | path: .cache 53 | 54 | - run: pip install mkdocs-material mkdocs-callouts mkdocs-glightbox mkdocs-git-revision-date-localized-plugin mkdocs-redirects pillow cairosvg 55 | - run: mkdocs gh-deploy --force 56 | - run: echo "🐙 ." 57 | 58 | # Summary and status 59 | - run: echo "🎨 MkDocs Publish Book workflow completed" 60 | - run: echo "🍏 Job status is ${{ job.status }}." 61 | -------------------------------------------------------------------------------- /.github/workflows/scheduled-version-check.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # ------------------------------------------ 3 | # Scheduled check of versions 4 | # - use as non-urgent report on versions 5 | # - Uses POSIX Cron syntax 6 | # - Minute [0,59] 7 | # - Hour [0,23] 8 | # - Day of the month [1,31] 9 | # - Month of the year [1,12] 10 | # - Day of the week ([0,6] with 0=Sunday) 11 | # 12 | # Using liquidz/anta to check: 13 | # - GitHub workflows 14 | # - deps.edn 15 | # ------------------------------------------ 16 | 17 | name: "Scheduled Version Check" 18 | on: 19 | schedule: 20 | # - cron: "0 4 * * *" # at 04:04:04 ever day 21 | # - cron: "0 4 * * 5" # at 04:04:04 ever Friday 22 | - cron: "0 4 1 * *" # at 04:04:04 on first day of month 23 | workflow_dispatch: # Run manually via GitHub Actions Workflow page 24 | 25 | jobs: 26 | scheduled-version-check: 27 | name: "Scheduled Version Check" 28 | runs-on: ubuntu-latest 29 | steps: 30 | - run: echo "🚀 Job automatically triggered by ${{ github.event_name }}" 31 | - run: echo "🐧 Job running on ${{ runner.os }} server" 32 | - run: echo "🐙 Using ${{ github.ref }} branch from ${{ github.repository }} repository" 33 | 34 | - name: Checkout Code 35 | uses: actions/checkout@v4 36 | with: 37 | fetch-depth: 0 38 | sparse-checkout: | 39 | .github 40 | - run: echo "🐙 ${{ github.repository }} repository sparse-checkout to the CI runner." 41 | - name: "Antq Check versions" 42 | uses: liquidz/antq-action@main 43 | with: 44 | excludes: "" 45 | skips: "boot clojure-cli pom shadow-cljs leiningen" 46 | 47 | # Summary 48 | - run: echo "🎨 library versions checked with liquidz/antq" 49 | - run: echo "🍏 Job status is ${{ job.status }}." 50 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Exclude all files from root directory 2 | /* 3 | 4 | # ------------------------ 5 | # Common project files 6 | !CHANGELOG.md 7 | !README.md 8 | !LICENSE 9 | 10 | # ------------------------ 11 | # Include MkDocs files 12 | !docs/ 13 | !includes/ 14 | !overrides/ 15 | !mkdocs.yml 16 | 17 | # ------------------------ 18 | # Project automation 19 | !Makefile 20 | 21 | # ------------------------ 22 | # Version Control 23 | !.gitignore 24 | !.gitattributes 25 | !.github/ 26 | 27 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## Unreleased 4 | - dev: spell lychee & repository trufflehog linters warn only (false positives) 5 | - dev: action/checkout v4 with sparse-checkout for megalinter 6 | - dev: sparse-checkout for publish-book workflow 7 | - dev: action/checkout v4 with sparse-checkout for changelog checker workflow 8 | - mkdocs: update emoji extension name for material 9.4 and update readme 9 | 10 | ### Added 11 | - building-api: add reitit overview and quick intro 12 | - dev: Makefile using Practicalli task definitions for books 13 | 14 | ### Changed 15 | - nav: refactor server-side-api to building-api 16 | 17 | * 2023-03-10 18 | ### Added 19 | - started a changelog 20 | ### Changed 21 | - [#90](https://github.com/practicalli/clojurescript/issues/90) convert ClojureScript book to MkDocs 22 | - Update figwheel logo name 23 | - Update ClojureScript REPL workflow image 24 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # ------------------------------------------ 2 | # Practicalli: Makefile 3 | # 4 | # Consistent set of targets to support local book development 5 | # ------------------------------------------ 6 | 7 | # .PHONY: ensures target used rather than matching file name 8 | # https://makefiletutorial.com/#phony 9 | .PHONY: all clean docs lint pre-commit-check test 10 | 11 | # ------- Makefile Variables --------- # 12 | # run help if no target specified 13 | .DEFAULT_GOAL := help 14 | SHELL := /usr/bin/zsh 15 | 16 | # Column the target description is printed from 17 | HELP-DESCRIPTION-SPACING := 24 18 | 19 | # Tool Commands 20 | MEGALINTER_RUNNER := npx mega-linter-runner --flavor documentation --env "'MEGALINTER_CONFIG=.github/config/megalinter.yaml'" --env "'VALIDATE_ALL_CODEBASE=true'" --remove-container 21 | MKDOCS_SERVER := mkdocs serve --dev-addr localhost:7777 22 | 23 | # Makefile file and directory name wildcard 24 | EDN-FILES := $(wildcard *.edn) 25 | # ------------------------------------ # 26 | 27 | # ------ Quality Checks ------------ # 28 | pre-commit-check: lint 29 | 30 | lint: ## Run MegaLinter with custom configuration (node.js required) 31 | $(info --------- MegaLinter Runner ---------) 32 | $(MEGALINTER_RUNNER) 33 | 34 | lint-fix: ## Run MegaLinter with custom configuration (node.js required) 35 | $(info --------- MegaLinter Runner ---------) 36 | $(MEGALINTER_RUNNER) --fix 37 | 38 | lint-clean: ## Clean MegaLinter report information 39 | $(info --------- MegaLinter Clean Reports ---------) 40 | - rm -rf ./megalinter-reports 41 | 42 | megalinter-upgrade: ## Upgrade MegaLinter config to latest version 43 | $(info --------- MegaLinter Upgrade Config ---------) 44 | npx mega-linter-runner@latest --upgrade 45 | # ------------------------------------ # 46 | 47 | # --- Documentation Generation ------ # 48 | python-venv: ## Enable Python Virtual Environment for MkDocs 49 | $(info --------- Mkdocs Local Server ---------) 50 | source ~/.local/venv/bin/activate 51 | 52 | docs: ## Build and run mkdocs in local server (python venv) 53 | $(info --------- Mkdocs Local Server ---------) 54 | source ~/.local/venv/bin/activate && $(MKDOCS_SERVER) 55 | 56 | docs-changed: ## Build only changed files and run mkdocs in local server (python venv) 57 | $(info --------- Mkdocs Local Server ---------) 58 | source ~/.local/venv/bin/activate && $(MKDOCS_SERVER) --dirtyreload 59 | 60 | docs-build: ## Build mkdocs (python venv) 61 | $(info --------- Mkdocs Local Server ---------) 62 | source ~/.local/venv/bin/activate && mkdocs build 63 | # ------------------------------------ # 64 | 65 | # ------------ Help ------------------ # 66 | # Source: https://nedbatchelder.com/blog/201804/makefile_help_target.html 67 | 68 | help: ## Describe available tasks in Makefile 69 | @grep '^[a-zA-Z]' $(MAKEFILE_LIST) | \ 70 | sort | \ 71 | awk -F ':.*?## ' 'NF==2 {printf "\033[36m %-$(HELP-DESCRIPTION-SPACING)s\033[0m %s\n", $$1, $$2}' 72 | # ------------------------------------ # 73 | -------------------------------------------------------------------------------- /content-plan.md: -------------------------------------------------------------------------------- 1 | # Content plan for Clojure WebApps book 2 | Thanks to the support from Clojurists Together the Clojure WebApps book is being updated to add more content and cover more libraries. 3 | 4 | Content will also move to using Clojure CLI tools and deps.edn projects, although the Leiningen content will remain available. 5 | 6 | ## Proposed content topics for the book update 7 | 8 | | Topics | Related projects and implementations | 9 | |------------------------------|-----------------------------------------------------------------------------------------| 10 | | Fundamentals | ring and compojure based web applications, covering routing, handlers, middleware, etc. | 11 | | Building API's | compojure-api template, openapi (swagger), prismatic schema, transit, jsonista | 12 | | Consuming API's | clojure.data.* , edn, transit, jsonista | 13 | | HTML content and templates | Hiccup and Selmer | 14 | | Web Scraping | Enilve and clojure.data.* | 15 | | Full Stack apps | Luminus template, ClojureScript UI, | 16 | | Data access | jdbc.next, migrations, | 17 | | WebSockets | | 18 | | High Performance | http-kit | 19 | | Alternative libraries | Yada, Bidi, Reitit | 20 | | Component Lifecycle | mount, component, integrant, roll your own | 21 | | Data Oriented service design | Reitit, Duct, Edge | 22 | | UI design and styling | CSS libraries (bulma, bootstrap, foundation), SVG graphics | 23 | 24 | 25 | ## Project ideas 26 | 27 | [Ideas for projects to implement](https://github.com/practicalli/clojure-webapps-content/issues) are most welcome 28 | 29 | * Dependency graph using SVG graphics 30 | * TODO: Kanban application 31 | * Status monitor (SVG Graphics) 32 | * User Management website (demonstrating how to make this usable for other projects) 33 | * Content API for video (targeted searching for finding very specific content on YouTube) 34 | * Financial services API's - online banking, personal insurance, stock tracking (mock stocks) 35 | * API's for a mobile application - Scoreboards, chat boards, rewards, profiles, evil chat bots 36 | -------------------------------------------------------------------------------- /docs/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/practicalli/clojure-web-services/3e9895ddcf4b4b7568e840759ce5be719c88c8b1/docs/.nojekyll -------------------------------------------------------------------------------- /docs/adding-more-route/using-cond-function.md: -------------------------------------------------------------------------------- 1 | # Using cond function 2 | 3 | Change the if function to `cond` and define additional routes you want to match on. We will show you a `/goodbye` route, feel free to add your own. 4 | 5 | Edit the `src/webdev/core.clj` file and update the `greet` function as follows 6 | 7 | ```clojure 8 | (defn greet 9 | "A function to process all requests for the web server. The default route / returns one message, /goodbye route another. for all other routes an error message is returned" 10 | [request] 11 | (cond 12 | (= "/" (:uri request)) 13 | {:status 200 14 | :body "Hello, Clojure World. I now update automatically" 15 | :headers {}} 16 | (= "/goodbye" (:uri request)) 17 | {:status 200 18 | :body "This is the end, my old friend" 19 | :headers {}} 20 | :else 21 | {:status 404 22 | :body "Sorry, page not found" 23 | :headers {}})) 24 | ``` 25 | 26 | Writing a big cond statement for all our routes would be really tedious and difficult to manage. So lets look at Compojure. 27 | -------------------------------------------------------------------------------- /docs/app-servers/app-server-logging.md: -------------------------------------------------------------------------------- 1 | # Application Server Logging 2 | 3 | * What to log in which environments 4 | * Logging levels 5 | * Logging as object rather than text 6 | * [mulog](https://github.com/BrunoBonacci/mulog){target=_blank} 7 | 8 | 9 | ## Simplistic logging 10 | 11 | `println` function sends information to the standard out and so is a very simple mechanism to create logs from specific parts of the application. This should be used sparingly and is no substitute for a specific logging framework. 12 | 13 | `println` can be useful in the REPL the standard out message as well as the evaluation result (`nil`) are shown. `println` can provide additional feedback for non-terminating processes that run in the REPL, such as an application server. 14 | 15 | ![Clojure WebApps application server startup via the REPL](https://raw.githubusercontent.com/practicalli/graphic-design/live/clojure-web-services/clojure-webapps-app-server-start-via-repl.png) 16 | 17 | ## Logging to Elastic Search / Kibana 18 | 19 | Log messages as objects, rather than text strings, provides greater sophistication by search tools as the messages have a structure. 20 | 21 | * [Elastisch - Clojure client for Elasticsearch](http://clojureelasticsearch.info/){target=_blank} and [GitHub repository](https://github.com/clojurewerkz/elastisch){target=_blank} 22 | * [Elasticsearch and Clojure: Getting Started](https://miguelmalvarez.com/2016/04/27/elasticsearch-and-clojure-getting-started/){target=_blank} - the practical academic 23 | * [Spandex - Elasticsearch new low level rest-client wrapper](https://github.com/mpenet/spandex){target=_blank} 24 | 25 | 26 | 27 | ## Problematic Practices 28 | 29 | Logging to the REPL - sending lots of logs to the REPL makes the REPL much harder to use directly 30 | 31 | Logging strings - logs entries are typically objects and far more searchable and discoverable that strings, so send objects to the logging service 32 | -------------------------------------------------------------------------------- /docs/app-servers/debugging.md: -------------------------------------------------------------------------------- 1 | # Debugging application servers 2 | 3 | 4 | 5 | 6 | 7 | ## Debugging handlers 8 | As handler functions are simply Clojure functions that take a request hash-map, those functions can be called from unit tests or the REPL to test they are working correctly. 9 | 10 | 11 | 12 | ## Ring mock 13 | Generate mock requests and responses (?) for testing handler functions 14 | 15 | 16 | 17 | 18 | ## Problematic Practices to avoid 19 | 20 | Using `(def name ,,,)` expressions for debugging is very bad, especially if those expressions are left in production code. 21 | 22 | 23 | `(println ,,,)` statements seem convenient however have very limited value. Using the REPL and REPL based debugging tools provide very useful output 24 | -------------------------------------------------------------------------------- /docs/app-servers/index.md: -------------------------------------------------------------------------------- 1 | # Application servers 2 | 3 | Application servers provide a common platform services to support server-side running of JVM applications (hence the term application server). 4 | 5 | These servers are often referred to more generically as web servers as they mostly work over http / https. 6 | 7 | Clojure uses embedded servers to support REPL Driven Development, so both new function definitions and server restarts can be managed within the context of a running REPL (avoiding the need to restart the REPL). 8 | 9 | ![Clojure WebApps simplified stack](https://raw.githubusercontent.com/practicalli/graphic-design/live/clojure-web-services/clojure-web-apps-stack.png) 10 | 11 | 12 | # Application components 13 | 14 | * Routing 15 | * Requests 16 | * Responses 17 | * Middleware 18 | 19 | 20 | ## Practicalli defacto library choices 21 | 22 | Practicalli defacto choices for building web services: 23 | 24 | 25 | | Library | Purpose | 26 | |:---------------|-----------------------------------------------------------------------------------------------| 27 | | ring/ring | Provides Jetty and Ring - managing requests and responses in Clojure using hash-maps | 28 | | metosin/reitit | Routing of request and responses, support for ring handlers and middleware (and interceptors) | 29 | 30 | 31 | ## Example Projects 32 | 33 | | Project | Description | 34 | |:-------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| 35 | | Status Monitor | Clojure CLI project using Httpkit, Compojure for routing, Hiccup and SVG graphics. Deployed via CircleCI on Heroku | 36 | | Banking On Clojure | Clojure CLI project using httpkit, Ring utilities, Compojure for routing. relational data store using next.jdbc, HoneySQL, clojure.spec & postgresql. Generative testing using clojure.spec | 37 | | ToDo app | Leiningen project using Ring (Jetty), Compojure for routing and Hiccup for HTML generation | 38 | -------------------------------------------------------------------------------- /docs/app-servers/middleware.md: -------------------------------------------------------------------------------- 1 | # Middleware 2 | 3 | Apply common transformations to request and or response hash-maps, such as security tokens, cookie management, session and access management, presentation templates, etc. 4 | 5 | Middleware is implemented by Clojure functions that receive a handler as an argument and return a handler as a result. 6 | 7 | 8 | ## Middleware in Ring 9 | 10 | Middleware can wrap handlers or other middleware, affecting their behavior. 11 | 12 | For example the `wrap-reload` middleware enables live reloading by detecting file changes and reloading affected functions into their namespace, before the request is passed to the relevant handler function 13 | 14 | Middleware provided by Ring includes: 15 | 16 | In `ring/ring-core`: 17 | 18 | * [wrap-cookies](https://github.com/mmcgrana/ring/blob/master/ring-core/src/ring/middleware/cookies.clj#L124) (ring.middleware.cookies) 19 | * [wrap-file](https://github.com/mmcgrana/ring/blob/master/ring-core/src/ring/middleware/file.clj#L14) (ring.middleware.file) 20 | * [wrap-file-info](https://github.com/mmcgrana/ring/blob/master/ring-core/src/ring/middleware/file_info.clj#L89) (ring.middleware.file-info) 21 | * [wrap-flash](https://github.com/mmcgrana/ring/blob/master/ring-core/src/ring/middleware/flash.clj#L4) (ring.middleware.flash) 22 | * [wrap-keyword-params](https://github.com/mmcgrana/ring/blob/master/ring-core/src/ring/middleware/keyword_params.clj#L15) (ring.middleware.keyword-params) 23 | * [wrap-multipart-params](https://github.com/mmcgrana/ring/blob/master/ring-core/src/ring/middleware/multipart_params.clj#L60) (ring.middleware.multipart-params 24 | * [wrap-nested-params](https://github.com/mmcgrana/ring/blob/master/ring-core/src/ring/middleware/nested_params.clj#L47) (ring.middleware.nested-params 25 | * [wrap-params](https://github.com/mmcgrana/ring/blob/master/ring-core/src/ring/middleware/params.clj#L54) (ring.middleware.params) 26 | * [wrap-session](https://github.com/mmcgrana/ring/blob/master/ring-core/src/ring/middleware/session.clj#L6) (ring.middleware.session) 27 | 28 | In `ring/ring-devel`: 29 | 30 | * [wrap-lint](https://github.com/mmcgrana/ring/blob/master/ring-devel/src/ring/middleware/lint.clj#L84) (ring.middleware.lint) 31 | * [wrap-reload](https://github.com/mmcgrana/ring/blob/master/ring-devel/src/ring/middleware/reload.clj#L4) (ring.middleware.reload) 32 | * [wrap-stacktrace](https://github.com/mmcgrana/ring/blob/master/ring-devel/src/ring/middleware/stacktrace.clj#L75) (ring.middleware.stacktrace) 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /docs/app-servers/routing-libraries.md: -------------------------------------------------------------------------------- 1 | ## Application Logic 2 | 3 | 4 | 5 | ![Ring - Compojure routes](../images/clojure-ring-adaptor-middleware-route--handler-overview.png) 6 | 7 | 8 | * [Routing](application-logic/routing.md) 9 | * [Requests](application-logic/requests/index.md) 10 | * [Responses](application-logic/responses/index.md) 11 | * [handlers](application-logic/handlers/index.md) 12 | * [middleware](application-logic/middleware/index.md) 13 | * [Serving static content](app-servers/static-content.md) 14 | 15 | 16 | 17 | 18 | 19 | 20 | ## Ring 21 | Ring is the defacto library for server-side web applications. Even if not using the Ring library, the contents that Ring established are used by other libraries. 22 | 23 | 24 | ## Compojure 25 | [Compojure](https://github.com/weavejester/compojure) is a library that works with Ring to manage 26 | Compojure also has convenience functions that make ring responses easier to generate. 27 | 28 | In this section we will update our project to use Compojure. 29 | 30 | 31 | ## Bidi - Bi-directional URI dispatch 32 | https://github.com/juxt/bidi 33 | Clojure and ClojureScript 34 | 35 | bidi is written to do 'one thing well' (URI dispatch and formation) and is intended for use with Ring middleware, HTTP servers (including Jetty, http-kit and aleph) and is fully compatible with Liberator. 36 | 37 | 38 | ## yada - resources as data 39 | [yada](https://github.com/juxt/yada) is a web library for Clojure, designed to support the creation of production services via HTTP. 40 | 41 | It has the following features: 42 | 43 | * Standards-based, comprehensive HTTP coverage (content negotiation, conditional requests, etc.) 44 | * Parameter validation and coercion, automatic Swagger support 45 | * Rich extensibility (methods, mime-types, security and more) 46 | * Asynchronous, efficient interceptor-chain design built on manifold 47 | * Excellent performance, suitable for heavy production workloads 48 | 49 | yada is a sibling library to bidi - whereas bidi is based on routes as data, yada is based on resources as data. 50 | 51 | 52 | ## Reitit 53 | A data approach to routing 54 | -------------------------------------------------------------------------------- /docs/app-servers/routing.md: -------------------------------------------------------------------------------- 1 | # Routing 2 | 3 | Compojure 4 | Bidi 5 | Reitit 6 | 7 | 8 | ## Injecting resources using routing 9 | 10 | 11 | ```clojure 12 | (defroutes 13 | GET /accounts/account [] (partial db/connection request)) 14 | ``` 15 | 16 | And then have a handler that takes both a request and resource arguments. 17 | This makes the handler pure in respect that it does not require any external data to do its job (yes the connection is external, but the reference to the connection is provided as an argument to the side effect is abstracted away). 18 | 19 | 20 | Middleware could also be used to wrap all the routes, however, if some routes do not use the database then this approach adds redundancy and makes the abstraction feel too high a level in the application design. This approach also makes it harder to test the handlers as normal Clojure functions, as its not possible to simply call that function with an argument. 21 | 22 | 23 | 24 | ## Resources 25 | * [How to manage database connections in Clojure](https://clojureverse.org/t/how-to-manage-database-connection-in-clojure/5067) - ClojureVerse 26 | 27 | * https://devcenter.heroku.com/articles/database-connection-pooling-with-clojure 28 | * https://stackoverflow.com/questions/19776462/passing-state-as-parameter-to-a-ring-handler 29 | -------------------------------------------------------------------------------- /docs/app-servers/set-listen-port.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/practicalli/clojure-web-services/3e9895ddcf4b4b7568e840759ce5be719c88c8b1/docs/app-servers/set-listen-port.md -------------------------------------------------------------------------------- /docs/app-servers/static-content.md: -------------------------------------------------------------------------------- 1 | # Static Content 2 | 3 | 4 | !!! HINT "Avoid serving large or complex static content" 5 | The most efficient and secure way of serving static content from a Clojure (or any other app) is to not server content directly. Using a static web server such as nginx or apache httpd provides a separation of concerns. 6 | 7 | Nginx and Apache Httpd provide many features for serving static content and managing mime types, etc which would have little value to implement inside a Clojure web application. 8 | 9 | Nginx and Apache Httpd can be configured as a reverse proxy, only redirecting specific request to the Clojure application 10 | -------------------------------------------------------------------------------- /docs/assets/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/practicalli/clojure-web-services/3e9895ddcf4b4b7568e840759ce5be719c88c8b1/docs/assets/android-chrome-192x192.png -------------------------------------------------------------------------------- /docs/assets/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/practicalli/clojure-web-services/3e9895ddcf4b4b7568e840759ce5be719c88c8b1/docs/assets/android-chrome-512x512.png -------------------------------------------------------------------------------- /docs/assets/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/practicalli/clojure-web-services/3e9895ddcf4b4b7568e840759ce5be719c88c8b1/docs/assets/apple-touch-icon.png -------------------------------------------------------------------------------- /docs/assets/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/practicalli/clojure-web-services/3e9895ddcf4b4b7568e840759ce5be719c88c8b1/docs/assets/favicon-16x16.png -------------------------------------------------------------------------------- /docs/assets/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/practicalli/clojure-web-services/3e9895ddcf4b4b7568e840759ce5be719c88c8b1/docs/assets/favicon-32x32.png -------------------------------------------------------------------------------- /docs/assets/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/practicalli/clojure-web-services/3e9895ddcf4b4b7568e840759ce5be719c88c8b1/docs/assets/favicon.ico -------------------------------------------------------------------------------- /docs/assets/images/practicalli-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/practicalli/clojure-web-services/3e9895ddcf4b4b7568e840759ce5be719c88c8b1/docs/assets/images/practicalli-logo.png -------------------------------------------------------------------------------- /docs/assets/images/social/README.md: -------------------------------------------------------------------------------- 1 | # Social Cards 2 | 3 | Social Cards are visual previews of the website that are included when sending links via social media platforms. 4 | 5 | Material for MkDocs is [configured to generate beautiful social cards automatically](https://squidfunk.github.io/mkdocs-material/setup/setting-up-social-cards/), using the colors, fonts and logos defined in `mkdocs.yml` 6 | 7 | Generated images are stored in this directory. 8 | -------------------------------------------------------------------------------- /docs/building-api/cheshire.md: -------------------------------------------------------------------------------- 1 | # Cheshire - fast JSON encoding 2 | 3 | [Cheshire](https://github.com/dakrone/cheshire) is fast JSON encoding library with support for Date/UUID/Set/Symbol encoding and SMILE support. 4 | 5 | * [Github repository and usage](https://github.com/dakrone/cheshire) 6 | * [API documentation](http://dakrone.github.io/cheshire/) 7 | -------------------------------------------------------------------------------- /docs/building-api/compojure-api-template.md: -------------------------------------------------------------------------------- 1 | # compojure-api template 2 | 3 | 4 | 5 | Quickly create the basics of a server-side webapp with the [compojure-api](https://github.com/metosin/compojure-api-template) template for Leiningen. 6 | 7 | ```bash 8 | lein new compojure-api project-name 9 | ``` 10 | 11 | This command creates a new Clojure project in a directory called **scoreboard-service**. 12 | 13 | 14 | ## Adding tests 15 | 16 | Using either of the `+clojure-test` or `+midge` will add the specific test library to the project created. 17 | 18 | ```bash 19 | lein new compojure-api project-name +clojure-test 20 | ``` 21 | -------------------------------------------------------------------------------- /docs/building-api/create-compojure-api-project.md: -------------------------------------------------------------------------------- 1 | # Create a compojure-api project 2 | 3 | > ####NOTE::Create a project with tests 4 | ```bash 5 | lein new compojure-api my-api +clojure-test 6 | ``` 7 | 8 | ## Deconstruct the project 9 | 10 | The project this template creates is relatively simple in terms of dependencies in the `project.clj` file 11 | 12 | ```clojure 13 | (defproject my-api "0.1.0-SNAPSHOT" 14 | :description "Experimenting with the compojure-api" 15 | :dependencies [[org.clojure/clojure "1.8.0"] 16 | [metosin/compojure-api "1.1.11"]] 17 | :ring {:handler my-api.handler/app} 18 | :uberjar-name "server.jar" 19 | :profiles {:dev {:dependencies [[javax.servlet/javax.servlet-api "3.1.0"] 20 | [cheshire "5.5.0"] 21 | [ring/ring-mock "0.3.0"]] 22 | :plugins [[lein-ring "0.12.0"]]}}) 23 | ``` 24 | 25 | Interesting things to note are its using the `lein-ring` plugin, so we should run the application with `lein ring server`. 26 | 27 | When we want to deploy the application then we should use the `lein ring uberjar` command to create an uberjar (a java archive file that includes our Clojure application and the `clojure.core` library, so we can just run it as a java library). 28 | 29 | ## `:dev` profile 30 | In the `:dev` profile, dependencies include [`ring/ring-mock`](https://github.com/ring-clojure/ring-mock) library to help us test our server-side web application. 31 | 32 | There is also the [`cheshire`](https://github.com/dakrone/cheshire) library to help us work with JSON data in an efficient way. 33 | -------------------------------------------------------------------------------- /docs/building-api/end-to-end-testing/curl.md: -------------------------------------------------------------------------------- 1 | # curl 2 | 3 | > #### TODO::work in progress, sorry 4 | > Pull requests are welcome 5 | -------------------------------------------------------------------------------- /docs/building-api/end-to-end-testing/httpie.md: -------------------------------------------------------------------------------- 1 | # HTTPie 2 | -------------------------------------------------------------------------------- /docs/building-api/end-to-end-testing/index.md: -------------------------------------------------------------------------------- 1 | # End to end API testing 2 | 3 | There are several tools for testing your API. 4 | 5 | | Tools | Description | 6 | |:----------------------------------------|:-------------------------------------------------------------------| 7 | | OpenAPI (Swagger) | Provides live documentation of an API and ability to run API calls | 8 | | curl | Command line tool for talking to the web (client side) | 9 | | [Insomnia.rest](https://insomnia.rest/) | HTTP and GraphQL toolbelt for debugging APIs (client side) | 10 | | [Postman](https://www.getpostman.com/) | Collaborative platform for API development | 11 | 12 | 13 | Open API should be built into any API you build as it provides living documentation of your API as you develop, as well as a way for developers to test queries against your API. 14 | 15 | curl is the classic command line tool for testing anything on the web. Its an excellent tool for one off tests or for writing a batch of tests in a script. 16 | 17 | Insomnia is a great tool to help you debug your API and generate code for API calls in over 30 different programming languages. 18 | 19 | Postman is aimed more at the corporate developer or someone dealing with a large set of APIs. It requires more setup although provides more features. 20 | -------------------------------------------------------------------------------- /docs/building-api/end-to-end-testing/postman.md: -------------------------------------------------------------------------------- 1 | # Postman 2 | 3 | > #### TODO::work in progress, sorry 4 | > Pull requests are welcome 5 | -------------------------------------------------------------------------------- /docs/building-api/end-to-end-testing/swagger.md: -------------------------------------------------------------------------------- 1 | # Swagger 2 | 3 | > #### TODO::work in progress, sorry 4 | > Pull requests are welcome 5 | -------------------------------------------------------------------------------- /docs/building-api/json-files.md: -------------------------------------------------------------------------------- 1 | # Working with JSON files 2 | 3 | slurp will read files into our Clojure code. 4 | 5 | ```clojure 6 | (slurp "spicy-vegan-pepperoni.json") 7 | ``` 8 | 9 | We can use cheshire library to convert the JSON to a Clojure data structure. 10 | 11 | ```clojure 12 | (cheshire/parse-string 13 | (slurp "spicy-vegan-pepperoni.json")) 14 | ;; => {"name" "Spicy Vegan Pepperoni", "size" "XL", "origin" {"country" "PO", "city" "Tampere"}, "description" "Healthy and delicious Vegan version of a double pepperoni pizza with some jalapenos to spice it up"} 15 | ``` 16 | 17 | Lets pretty print the Clojure data structure to make it easier to read 18 | 19 | ```clojure 20 | (clojure.pprint/pprint 21 | (cheshire/parse-string 22 | (slurp "spicy-vegan-pepperoni.json"))) 23 | ;; => nil 24 | 25 | ;; From the REPL output 26 | {"name" "Spicy Vegan Pepperoni", 27 | "size" "XL", 28 | "origin" {"country" "PO", "city" "Tampere"}, 29 | "description" 30 | "Healthy and delicious Vegan version of a double pepperoni pizza with some jalapenos to spice it up"} 31 | 32 | ``` 33 | -------------------------------------------------------------------------------- /docs/building-api/plumatic-schema.md: -------------------------------------------------------------------------------- 1 | # Plumatic schema - defining the shape of data 2 | 3 | As an API is an external system then it is important to define the shape of data coming into and leaving the application. 4 | 5 | [Plumatic schema](https://github.com/plumatic/schema) is a simple way to define the shape of data in Clojure without having to define fixed static types. 6 | 7 | ## Dice roll result 8 | 9 | In this example. our API is related to a game and we call our API to get the result of a dice roll 10 | 11 | ```clojure 12 | (s/defschema DiceRollResult 13 | {:result s/Int}) 14 | ``` 15 | 16 | ## Customer 17 | 18 | Most business systems (and most systems in general) have some concept of a user or customer. Here we define a schema for a valid customer. 19 | 20 | In this example, a valid customer lives in one of two cities, as defined using an schema enumeration (enum) 21 | 22 | ```clojure 23 | (s/defschema Customer {:id s/Str, 24 | :name s/Str 25 | :address {:street s/Str 26 | :city (s/enum :maidstone :dover)}}) 27 | ``` 28 | 29 | ## Pizza Order 30 | 31 | We can make the data be as specific or as general as we need. Enumerations allow us to limit the set of valid options. If there were a lot of options then it may be useful to define them as a data structure in their own namespace. 32 | 33 | ```clojure 34 | (s/defschema Pizza 35 | {:name s/Str 36 | :size (s/enum :L :M :S) 37 | :origin {:country (s/enum :FI :PO) 38 | :city s/Str} 39 | (s/optional-key 40 | :description) s/Str}) 41 | 42 | (s/defschema Customer {:id s/Str, 43 | :name s/Str 44 | :address {:street s/Str 45 | :city (s/enum :maidstone :dover)}}) 46 | ``` 47 | 48 | ## Legitimate Ferry Company 49 | 50 | We can use the data we define to ensure that something is valid. For example, if a Ferry company uses this API to register themselves as a business, we can ensure we capture the number of ferries they have. 51 | 52 | In the logic of our API we can use the number of ferries value to check if we should register this company. If it has no ferries, then we shouldn't register the company. 53 | 54 | ```clojure 55 | (s/defschema FerryCompany 56 | {:name s/Str 57 | :number-of-ferries Long 58 | :country (s/enum :UK :France :Netherlands) 59 | (s/optional-key 60 | :description) s/Str}) 61 | ``` 62 | -------------------------------------------------------------------------------- /docs/building-api/projects/game-scoreboard-ui/create-project.md: -------------------------------------------------------------------------------- 1 | # Create new project using figwheel-main 2 | 3 | Create a new project using figwheel-main, the newest version of figwheel. 4 | 5 | Include the `reagent` library to make the project a single page app in the style of react.js. 6 | 7 | 8 | ```bash 9 | lein new figwheel-main game-scoreboard-ui -- --reagent 10 | ``` 11 | 12 | ![Game scoreboard new Leiningen project with figwheel-main and reagent](/images/game-scoreboard-ui--leiningen-new-project-figwheel-main.png) 13 | 14 | 15 | Change into the 'game-scoreboard-ui' directory and run 'lein fig:build' 16 | 17 | 18 | ```bash 19 | cd game-scoreboard-ui 20 | lein fig:build 21 | ``` 22 | 23 | Your default browser will open at [localhost:9500](http://localhost:9500/) 24 | 25 | 26 | ![Game Scoreboard UI - new project website](/images/game-scoreboard-ui--new-project-website.png) 27 | -------------------------------------------------------------------------------- /docs/building-api/projects/game-scoreboard-ui/index.md: -------------------------------------------------------------------------------- 1 | # Game Scoreboard UI 2 | 3 | Create a Web user interface for the Scoreboard so we can test out the API and do something interesting with the results. 4 | 5 | - Create a project using figwheel-main 6 | -------------------------------------------------------------------------------- /docs/building-api/projects/game-scoreboard/defining-scoreboard.md: -------------------------------------------------------------------------------- 1 | # Defining Scores and a Scoreboard 2 | 3 | We use the Plumatic schema to define what a score looks like, as well as what the overall scoreboard looks like. 4 | 5 | ## Scores 6 | 7 | A score is an whole number (Integer) that represents the score achieved for a particular game 8 | 9 | ```clojure 10 | (schema/defschema Score 11 | {:player-id schema/Uuid 12 | :score schema/Int 13 | 14 | (schema/optional-key 15 | :gravitar) schema/Str}) 16 | ``` 17 | 18 | ## Leaderboard 19 | 20 | The Leader board is a collection of scores for a game. The scoreboard is ordered by highest value by default. 21 | 22 | 23 | 24 | 25 | 26 | ## Player 27 | 28 | 29 | ## Player accounts 30 | -------------------------------------------------------------------------------- /docs/building-api/projects/game-scoreboard/defining-scores.md: -------------------------------------------------------------------------------- 1 | # Defining Scores 2 | 3 | > #### TODO::work in progress, sorry 4 | -------------------------------------------------------------------------------- /docs/building-api/projects/game-scoreboard/index.md: -------------------------------------------------------------------------------- 1 | # Create a new project with compojure-api template 2 | 3 | You can quickly create the basics of a server-side webapp with the [compojure-api](https://github.com/metosin/compojure-api-template) template for Leiningen. 4 | 5 | ```bash 6 | lein new compojure-api game-scoreboard +clojure-test 7 | ``` 8 | 9 | This command creates a new Clojure project in a directory called **game-scoreboard**. 10 | 11 | 12 | ## Update Clojure version 13 | 14 | Edit the `project.clj` file and update the `org.clojure/clojure` dependency to `1.10.0` 15 | -------------------------------------------------------------------------------- /docs/building-api/ring-mock.md: -------------------------------------------------------------------------------- 1 | # ring-mock 2 | 3 | [`ring-mock`](https://github.com/ring-clojure/ring-mock) is a testing library for server-side applications 4 | 5 | 6 | Ring-Mock creates Ring request maps to assist with defining tests in Clojure. 7 | 8 | ## Installation 9 | 10 | Add the following development dependency to your `project.clj` file: 11 | 12 | ```clojure 13 | [ring/ring-mock "0.3.2"] 14 | ``` 15 | 16 | ## Examples 17 | 18 | ```clojure 19 | (ns your-app.core-test 20 | (:require [clojure.test :refer :all] 21 | [your-app.core :refer :all] 22 | [ring.mock.request :as mock])) 23 | 24 | (deftest your-handler-test 25 | (is (= (your-handler (mock/request :get "/doc/10")) 26 | {:status 200 27 | :headers {"content-type" "text/plain"} 28 | :body "Your expected result"}))) 29 | 30 | (deftest your-json-handler-test 31 | (is (= (your-handler (-> (mock/request :post "/api/endpoint") 32 | (mock/json-body {:foo "bar"}))) 33 | {:status 201 34 | :headers {"content-type" "application/json"} 35 | :body {:key "your expected result"}}))) 36 | ``` 37 | -------------------------------------------------------------------------------- /docs/building-api/ring-swagger.md: -------------------------------------------------------------------------------- 1 | # ring-swagger 2 | 3 | [`ring-swagger`](https://github.com/metosin/ring-swagger) is a Swagger 2.0 implementation for Clojure/Ring using Plumatic Schema (support for clojure.spec via spec-tools. 4 | 5 | * Transforms deeply nested Schemas into Swagger JSON Schema definitions 6 | * Extended & symmetric JSON & String serialization & coercion 7 | * Middleware for handling Schemas Validation Errors & Publishing swagger-data 8 | * Local api validator 9 | * Swagger artifact generation 10 | * swagger.json via ring.swagger.swagger2/swagger-json 11 | * Swagger UI bindings. (get the UI separately as jar or from NPM) 12 | 13 | ## Documentation 14 | 15 | * [`ring-swagger` API documentation](http://metosin.github.io/ring-swagger/doc/) 16 | -------------------------------------------------------------------------------- /docs/building-api/swagger.md: -------------------------------------------------------------------------------- 1 | # Swagger - self describing APIs 2 | 3 | 4 | 5 | ![Swagger UI](https://www.anthony-galea.com/images/2016-04-28-swagger.png) 6 | -------------------------------------------------------------------------------- /docs/building-api/terminology.md: -------------------------------------------------------------------------------- 1 | # Terminology 2 | 3 | ## Application Programming Interface (API) 4 | 5 | An API defines how to use another piece of software. The API shows you the public functions and data you can use with your own software, allowing you to do more with less code. 6 | 7 | https://en.wikipedia.org/wiki/Application_programming_interface 8 | 9 | 10 | ## Uniform Resource Identifier (URI) 11 | 12 | A Uniform Resource Identifier (URI) is a string of characters that unambiguously identifies a particular resource. To guarantee uniformity, all URIs follow a predefined set of syntax rules,[1] but also maintain extensibility through a separately defined hierarchical naming scheme (e.g. "http://"). 13 | 14 | https://en.wikipedia.org/wiki/Uniform_Resource_Identifier 15 | 16 | ## Uniform Resource Locator (URL) 17 | 18 | A web page and the images and videos it contains all have their own URL, a specific address where they can be found on the Internet. 19 | 20 | A URL is a specific form of URI for web pages and the content that they contain. 21 | 22 | https://en.wikipedia.org/wiki/URL 23 | -------------------------------------------------------------------------------- /docs/building-api/testing-api.md: -------------------------------------------------------------------------------- 1 | # Testing our API 2 | 3 | We used the `clojure-test` option when we created the project, so we will use this built in library. 4 | 5 | 6 | ## Writing tests 7 | 8 | Writing tests is just the same as other Clojure applications. 9 | 10 | 11 | ```clojure 12 | (deftest a-test 13 | 14 | (testing "Test GET request to /hello?name={a-name} returns expected response" 15 | (let [response (app (-> (mock/request :get "/api/plus?x=1&y=2"))) 16 | body (parse-body (:body response))] 17 | (is (= (:status response) 200)) 18 | (is (= (:result body) 3))))) 19 | ``` 20 | 21 | ## Using helper functions 22 | 23 | It is good practice to create helper functions to extract out common code into its onw function. This saves on duplication, reduces maintenance and should improve the readability of your tests. 24 | 25 | 26 | Here is an example of a helper function that reads data in the form of JSON and creates a Clojure map for us to work with. 27 | 28 | ```clojure 29 | (defn parse-body [body] 30 | (cheshire/parse-string (slurp body) true)) 31 | ``` 32 | 33 | > ####HINT::Cheshire API 34 | > See the [parse-string description in the Cheshire API documentation](http://dakrone.github.io/cheshire/cheshire.core.html#var-parse-string) 35 | 36 | 37 | ## Including test libraries in the namespace 38 | 39 | Including the testing libraries is standard `:require` statements. 40 | 41 | ```clojure 42 | (ns my-api.core-test 43 | (:require [cheshire.core :as cheshire] 44 | [clojure.test :refer :all] 45 | [my-api.handler :refer :all] 46 | [ring.mock.request :as mock])) 47 | ``` 48 | 49 | 50 | ## `ring.mock` library 51 | 52 | A library to help you mock parts of your server-side application. This works just as well for APIs as web applications. 53 | 54 | 55 | > ####HINT::Writing files in Clojure with spit 56 | > [`spit`](https://clojuredocs.org/clojure.core/spit) is a simple function that will write files. 57 | -------------------------------------------------------------------------------- /docs/clojure-databases/crux/index.md: -------------------------------------------------------------------------------- 1 | # Crux - bi-temporal schema-less document database 2 | [Crux](https://opencrux.com/) is a general purpose database with graph-oriented bi-temporal indexes. Datalog, SQL & EQL queries are supported along with Java, HTTP & Clojure APIs. The Datalog query interface that can be used to express complex joins and recursive graph traversals. 3 | 4 | ## Getting Started 5 | Follow the [Crux Earth Assignment Tutorial](https://juxt.pro/blog/crux-tutorial-setup), in either the self-contained [Next-Journal environment](https://nextjournal.com/crux-tutorial/) or as your own Clojure project. 6 | 7 | {% tabs clojure="Clojure CLI tools", lein="Leiningen" %} 8 | 9 | {% content "clojure" %} 10 | Using the Clojure CLI tools and practicalli/clojure-deps-edn configuration, create a new project: 11 | 12 | ```bash 13 | clojure -X:project/new :template app :name practicalli/crux-demo 14 | ``` 15 | 16 | {% content "lein" %} 17 | Using the Leiningen build tool, create a new project: 18 | 19 | ```bash 20 | lein new app practicalli/crux-demo 21 | ``` 22 | 23 | {% endtabs %} 24 | 25 | [Install Crux](https://opencrux.com/reference/installation.html) as a library in a Clojure project or use the pre-built docker image. 26 | 27 | > Note: to have more than one set of tabs in a page, simply create unique id's for the tabs, e.g. practicalli2 28 | 29 | 30 | Experiment with the [Crux-labs workshop project](https://github.com/crux-labs/reclojure-workshop), which contains examples of using Crux. 31 | 32 | 33 | ## Resources 34 | * [Library dependency (clojars)](https://github.com/juxt/crux/releases) 35 | * [Reference Documentation](https://opencrux.com/reference) 36 | * [Community discussions (Zulip)](https://juxt-oss.zulipchat.com/#narrow/stream/194466-crux) 37 | * [GitHub discussions](https://github.com/juxt/crux/discussions) 38 | 39 | {% youtube %} 40 | https://www.youtube.com/watch?v=JkZfQZGLPTA 41 | {% endyoutube %} 42 | 43 | 44 | ## Unbundled architectural overview 45 | Crux follows an unbundled architectural, decoupled components communicating via an immutable log and document store. `crux-rocksdb` is the high performance default data store, with a range of storage options available for embedded usage and cloud scaling. 46 | 47 | Crux embraces the transaction log as the central point of coordination when running as a distributed system. Use of a separate document store enables simple eviction of active and historical data to assist with technical compliance for information privacy regulations. 48 | 49 | This design makes it feasible and desirable to embed Crux nodes directly within your application processes, which reduces deployment complexity and eliminates round-trip overheads when running complex application queries. 50 | 51 | ![Crux - unbundled architectural overview](https://raw.githubusercontent.com/juxt/crux/master/docs/about/modules/ROOT/images/crux-node-1.svg) 52 | -------------------------------------------------------------------------------- /docs/clojure-databases/index.md: -------------------------------------------------------------------------------- 1 | # Clojure Databases 2 | 3 | | Database | Description | 4 | |--------------------|--------------------------------------------------------------| 5 | | [Juxt Crux](crux/) | Bi-temporal schema-less high performance CQRS style database | 6 | | Onyx | | 7 | | Cognitect Datomic | (commercial product) | 8 | -------------------------------------------------------------------------------- /docs/full-app/index.md: -------------------------------------------------------------------------------- 1 | # Building a full database backed app 2 | -------------------------------------------------------------------------------- /docs/libraries/reitit/constructing-routes.md: -------------------------------------------------------------------------------- 1 | # Reitit: Constructing routes 2 | 3 | Create a simple Clojure project 4 | 5 | ```bash 6 | clojure -T:project/new :template app :name practicalli/reitit-routing 7 | ``` 8 | 9 | Require reitit 10 | 11 | ```clojure 12 | (require '[reitit.core :as reitit]) 13 | ``` 14 | 15 | Define several simple routes using reitit router 16 | 17 | Routes are defined as a collection (vector) of vectors, with each vector defining the path of the route and an optional name. 18 | 19 | `reitit.core/router` creates a router from the collection of vectors and an optional hash-map of routes configuration options (e.g middleware) 20 | 21 | ```clojure 22 | (def router 23 | (reitit/router 24 | [["/api/ping" ::ping] 25 | ["/api/game-scoreboard/:score-id" ::game-score]])) 26 | ``` 27 | 28 | > names are used to provide a unique way of referring to a route throughout the whole project, as they are a namespace qualified keyword 29 | 30 | Selects implementation based on route details. The following options are available: 31 | 32 | | Key | description | 33 | |--------------|-------------------------------------------------------------------------------------------------------------| 34 | | `:path` | Base-path for routes | 35 | | `:routes` | Initial resolved routes (default []) | 36 | | `:data` | Initial route data (default {}) | 37 | | `:spec` | clojure.spec definition for a route data, see reitit.spec on how to use this | 38 | | `:syntax` | Path-parameter syntax as keyword or set of keywords (default #{:bracket :colon}) | 39 | | `:expand` | Function of arg opts => data to expand route arg to route data (default reitit.core/expand) | 40 | | `:coerce` | Function of route opts => route to coerce resolved route, can throw or return nil | 41 | | `:compile` | Function of route opts => result to compile a route handler | 42 | | `:validate` | Function of routes opts => () to validate route (data) via side-effects | 43 | | `:conflicts` | Function of {route #{route}} => () to handle conflicting routes | 44 | | `:exception` | Function of Exception => Exception to handle creation time exceptions (default reitit.exception/exception) | 45 | | `:router` | Function of routes opts => router to override the actual router implementation | 46 | 47 | 48 | Routes can be found by either path or name 49 | -------------------------------------------------------------------------------- /docs/libraries/reitit/index.md: -------------------------------------------------------------------------------- 1 | # Reitit - fast data driven routing for Clojure and ClojureScript 2 | -------------------------------------------------------------------------------- /docs/micro-framework/edge/index.md: -------------------------------------------------------------------------------- 1 | # JUXT Edge 2 | 3 | > #### TODO::work in progress, sorry 4 | > Pull requests are welcome 5 | 6 | 7 | JUXT Edge is a foundation for Clojure projects, using leading edge libraries and with upgrade path as the Edge project evolves. 8 | 9 | Projects are created with Clojure CLI and tools.deps 10 | -------------------------------------------------------------------------------- /docs/micro-framework/index.md: -------------------------------------------------------------------------------- 1 | # Micro-frameworks 2 | 3 | > #### TODO::work in progress, sorry 4 | > Pull requests are welcome 5 | 6 | 7 | Introducing common micro-frameworks (curated libraries and configuration) as a basis for your own projects. 8 | 9 | * Luminus 10 | * Pedestal 11 | * JUXT Edge 12 | -------------------------------------------------------------------------------- /docs/micro-framework/luminus/index.md: -------------------------------------------------------------------------------- 1 | # Luminus 2 | 3 | > #### TODO::work in progress, sorry 4 | > Pull requests are welcome 5 | 6 | 7 | [Luminus](https://luminusweb.com/) is a clojure micro-framework based on a set of lightweight libraries. It ams to provide a robust and configurable template to generate web services and applications. 8 | 9 | Luminus also supports ClojureScript for browser based and Mobile UI's. 10 | 11 | 12 | > #### Hint::Luminus uses Leiningen 13 | > TODO: review how easy it would be to convert this to a tools.deps project. 14 | -------------------------------------------------------------------------------- /docs/micro-framework/pedestal/index.md: -------------------------------------------------------------------------------- 1 | # Pedestal 2 | 3 | > #### TODO::work in progress, sorry 4 | > Pull requests are welcome 5 | -------------------------------------------------------------------------------- /docs/project-url-shortner/add-alias-to-database.md: -------------------------------------------------------------------------------- 1 | # add alias to database 2 | 3 | -------------------------------------------------------------------------------- /docs/project-url-shortner/add-static-resources.md: -------------------------------------------------------------------------------- 1 | # Add static resources 2 | 3 | -------------------------------------------------------------------------------- /docs/project-url-shortner/alias-generator.md: -------------------------------------------------------------------------------- 1 | # Alias generator 2 | 3 | -------------------------------------------------------------------------------- /docs/project-url-shortner/compojure-template.md: -------------------------------------------------------------------------------- 1 | # Compojure Template 2 | 3 | 4 | ## Compojure API 5 | 6 | https://weavejester.github.io/compojure/index.html 7 | -------------------------------------------------------------------------------- /docs/project-url-shortner/create-database.md: -------------------------------------------------------------------------------- 1 | # create database 2 | 3 | -------------------------------------------------------------------------------- /docs/project-url-shortner/create-html-form.md: -------------------------------------------------------------------------------- 1 | # Create HTML Form 2 | 3 | -------------------------------------------------------------------------------- /docs/project-url-shortner/create-project.md: -------------------------------------------------------------------------------- 1 | # Create project 2 | 3 | To create a web service we can use two commonly used libraries in Clojure, [ring](/introducing-ring/) and [compojure](/compojure/). 4 | 5 | Ring provides many low-level functions to manage web requests and responses as well as providing an embedded web server (ie. Jetty). Most importantly it abstracts away all the complicated details of HTTP communication. So as a developer of the web app you mostly focus on processing a **request map** and returning a **response map**. 6 | 7 | Compojure provides a simple way to define routes for your application, eg what function is called when a browser requests a specific url. 8 | 9 | > ####Note:: Create a new project using Leiningen and the compojure template, then go into the project created. 10 | 11 | 12 | ```bash 13 | lein new compojure shorturl-service 14 | 15 | cd shorturl-service 16 | ``` 17 | 18 | Open the `project.clj` file in an editor and take a look at the dependencies added to the project. 19 | 20 | ```clojure 21 | (defproject shorturl-service "0.1.0-SNAPSHOT" 22 | :description "FIXME: write description" 23 | :url "http://example.com/FIXME" 24 | :min-lein-version "2.0.0" 25 | :dependencies [[org.clojure/clojure "1.8.0"] 26 | [compojure "1.5.1"] 27 | [ring/ring-defaults "0.2.1"]] 28 | :plugins [[lein-ring "0.9.7"]] 29 | :ring {:handler shorturl-service.handler/app} 30 | :profiles 31 | {:dev {:dependencies [[javax.servlet/servlet-api "2.5"] 32 | [ring/ring-mock "0.3.0"]]}}) 33 | ``` 34 | 35 | Apart from Clojure itself, the compojure and ring libraries have been added. 36 | 37 | The `:plugins` section adds `lein-ring` which allows us to run the server using the command `lein ring server` 38 | 39 | The `:ring` section defines the default function to call when running the project 40 | 41 | The `:profiles` section adds libraries useful for development and testing. 42 | -------------------------------------------------------------------------------- /docs/project-url-shortner/delete-alias-from-database.md: -------------------------------------------------------------------------------- 1 | # delete alias from database 2 | 3 | -------------------------------------------------------------------------------- /docs/project-url-shortner/design-data-structure.md: -------------------------------------------------------------------------------- 1 | # Design data structure 2 | 3 | One of the first decisions is how to design the data structure to hold our short url and full url addresses. 4 | 5 | ## The simplest approach 6 | 7 | We could create a really simple data structure with a vector 8 | 9 | ```clojure 10 | ["goouk" "https://duckduckgo.com/"] 11 | ``` 12 | 13 | In order to hold multiple short url mappings, we could have a vector of vectors 14 | 15 | ```clojure 16 | [["duckduckgo" "https://duckduckgo.com/"] 17 | ["practicalli" "https://practical.li"] 18 | ["slashdot" "https://slashdot.com"]] 19 | ``` 20 | Although the above is nice and simple, it does not provide any context for the data. 21 | 22 | ## Using a map for context 23 | 24 | The keys in a map can add specific meaning and context to the design of the data structure. 25 | 26 | Here are two examples, the first with keys as strings the second as keys as keywords. 27 | 28 | ```clojure 29 | {"short-url" "practicalli" "full-url" "http://practical.li"} 30 | 31 | {:short-url "practicalli" :full-url "http://practical.li"} 32 | ``` 33 | 34 | As keys need to be unique, then if we have multiple short url mappings to contain, then we need another data structure for each map. 35 | 36 | 37 | ```clojure 38 | [{:short-url "practicalli" :full-url "http://practical.li"} 39 | {:short-url "slashdot" :full-url "https://slashdot.org"}] 40 | ``` 41 | 42 | In the above design we cannot use a single map as all the keys in a map need to be unique 43 | 44 | ## Simplifying the map 45 | 46 | As keys must be unique in a map, we cannot have multiple keys called `:short-url`, therefore using that design we cant have a single map. 47 | 48 | We could simplify the map and remove the current keys and use the value for the short-url as the key and the full url as the value. This means we could just have a single map for all our short-url mappings. 49 | 50 | ```clojure 51 | {"practicalli" "http://practical.li" 52 | "slashdot" "https://slashdot.org" 53 | "duckduckgo" "https://duckduckgo.com/"} 54 | ``` 55 | 56 | Using Clojure keywords for the keys would also allow us to look up the full url addresses using the feature of maps that make keywords act like functions. This feature of the keyword in a map is just like calling the `get` function on the map with a specific key. 57 | 58 | ```clojure 59 | (def url-map 60 | {:practicalli "http://practical.li" 61 | :slashdot "https://slashdot.org" 62 | :duck-duck-go "https://duckduckgo.com/"}) 63 | 64 | (get url-map :practicalli) 65 | ;; => "http://practicalli.co.uk" 66 | 67 | (url-map :practicalli) 68 | ;; => "http://practicalli.co.uk" 69 | 70 | (:practicalli url-map ) 71 | ;; => "http://practicalli.co.uk" 72 | ``` 73 | -------------------------------------------------------------------------------- /docs/project-url-shortner/get-alias-from-database.md: -------------------------------------------------------------------------------- 1 | # get alias from database 2 | 3 | -------------------------------------------------------------------------------- /docs/project-url-shortner/html-form.md: -------------------------------------------------------------------------------- 1 | # HTML Form 2 | 3 | -------------------------------------------------------------------------------- /docs/project-url-shortner/if-let-function.md: -------------------------------------------------------------------------------- 1 | # if-let function 2 | 3 | -------------------------------------------------------------------------------- /docs/project-url-shortner/index.md: -------------------------------------------------------------------------------- 1 | # Project: URL Shortner as a Service 2 | 3 | In this section we will build a webservice to create short url's for web addresses, as with services such as [bit.ly](https://bit.ly). 4 | 5 | ![Short URL services](http://www.strategic-planet.com/wp-content/uploads/2013/03/url-shortener.png) 6 | 7 | The web service will also manage the redirection of your browser from the short url to the real web address. 8 | 9 | This project will take the simplest approach and is therefore not attempting to build a production ready service. 10 | -------------------------------------------------------------------------------- /docs/project-url-shortner/named-alias-handler.md: -------------------------------------------------------------------------------- 1 | # Named alias handler 2 | 3 | -------------------------------------------------------------------------------- /docs/project-url-shortner/persist-aliases.md: -------------------------------------------------------------------------------- 1 | # Persist aliases 2 | 3 | -------------------------------------------------------------------------------- /docs/project-url-shortner/postgres-setup.md: -------------------------------------------------------------------------------- 1 | # Postgres setup 2 | 3 | -------------------------------------------------------------------------------- /docs/project-url-shortner/redirect-to-full-url.md: -------------------------------------------------------------------------------- 1 | # Redirect short URL to full web address 2 | 3 | Adding a redirect is very easy to do with ring, as the ring library provides a function called `redirect` that takes a url as an argument 4 | 5 | > ####Note:: Include the `ring.util.response/redirect` function into the `shorturl-service.handler` namespace so that we can simply call the `redirect` function 6 | 7 | ```clojure 8 | (ns shorturl-service.handler 9 | (:require [compojure.core :refer :all] 10 | [compojure.route :as route] 11 | [ring.middleware.defaults :refer [wrap-defaults site-defaults]] 12 | [ring.handler.dump :refer [handle-dump]] 13 | [ring.util.response :refer [redirect]])) 14 | ``` 15 | -------------------------------------------------------------------------------- /docs/project-url-shortner/redis-setup.md: -------------------------------------------------------------------------------- 1 | # Redis setup 2 | 3 | -------------------------------------------------------------------------------- /docs/project-url-shortner/refacor-hiccup-form.md: -------------------------------------------------------------------------------- 1 | # Refactor: Hiccup form 2 | 3 | -------------------------------------------------------------------------------- /docs/project-url-shortner/return-short-url.md: -------------------------------------------------------------------------------- 1 | # Return short URL 2 | 3 | -------------------------------------------------------------------------------- /docs/project-url-shortner/return-url-aliases.md: -------------------------------------------------------------------------------- 1 | # Return URL aliases 2 | 3 | -------------------------------------------------------------------------------- /docs/project-url-shortner/run-project.md: -------------------------------------------------------------------------------- 1 | # Run project 2 | 3 | The compojure template provides a working webserver and simple webapp out of the box. 4 | 5 | > ####Note:: Run the project to start the server and webapp 6 | 7 | ```bash 8 | lein ring server 9 | ``` 10 | 11 | If you have not used Compojure or Ring previously, then it may take a few seconds to download their libraries from the Internet before starting the Jetty web server. 12 | 13 | You should see a output after the leiningen command showing you that the server has started 14 | 15 | ```bash 16 | 2016-07-15 13:44:02.242:INFO:oejs.Server:jetty-7.6.13.v20130916 17 | 2016-07-15 13:44:02.313:INFO:oejs.AbstractConnector:Started SelectChannelConnector@0.0.0.0:3000 18 | Started server on port 3000 19 | ``` 20 | 21 | Your default browser should also open at http://localhost:3000 with a message saying "Hello World". If your browser does not open then check for errors in the terminal where you ran the leiningen command. 22 | -------------------------------------------------------------------------------- /docs/project-url-shortner/test-app-reloading.md: -------------------------------------------------------------------------------- 1 | # Test app reloading 2 | 3 | The compojure template added several middleware functions to our project to make the webapp easier to work with. The `wrap-reload` middleware picks up changes to our code and will automatically load them into our running webapp. This provides rapid feedback on your coding. 4 | 5 | > ####Note:: Make a simple change to the `app-routes` function in `src/shorturl-service/handler.clj` file, eg. change the "Hello World" string to "Hello reloaded World" 6 | 7 | ```clojure 8 | (defroutes app-routes 9 | (GET "/" [] "Hello reloaded World") 10 | (route/not-found "Not Found")) 11 | ``` 12 | 13 | Now, refresh your browser and see the changes made. 14 | 15 | > ####Hint:: You should only need to restart the server again if you add libraries or define code outside of the scope of the `app`. Or if your code crashes the server, but I am sure that wont happen :) 16 | -------------------------------------------------------------------------------- /docs/project-url-shortner/using-ring-redirect.md: -------------------------------------------------------------------------------- 1 | # Using Ring Redirect 2 | 3 | -------------------------------------------------------------------------------- /docs/project-url-shortner/whats-in-a-request.md: -------------------------------------------------------------------------------- 1 | # What is in a Request 2 | 3 | The ring library converts the HTTP request into a clojure map, making it really easy to extract some or all of the values out of the request map. 4 | 5 | ## ring parameters 6 | 7 | The wrap-params middleware function adds support for url-encoded parameters. 8 | 9 | URL-encoded parameters are the primary way browsers pass values to web applications. These parameters are sent when a user submits a form. 10 | 11 | When applied to a handler, the parameter middleware adds three new keys to the request map: 12 | 13 | * :query-params - A map of parameters from the query string 14 | * :form-params - A map of parameters from submitted form data 15 | * :params - A merged map of all parameters 16 | 17 | ## viewing ring parameters 18 | 19 | You could write a function to simply display all the parameters as the body of the response. However Ring already provides a function to do this called `handle-dump` 20 | 21 | To use the `handle-dump` function, first include the function in the namespace using `(:require [ring.handler.dump :refer [handle-dump]])` 22 | 23 | > ####Note:: Add the `ring.handler.dump/handle-dump` function into the `shorturl-service.handler` namespace along with other require statements 24 | 25 | ```clojure 26 | (ns shorturl-service.handler 27 | (:require [compojure.core :refer :all] 28 | [compojure.route :as route] 29 | [ring.middleware.defaults :refer [wrap-defaults site-defaults]] 30 | [ring.handler.dump :refer [handle-dump]])) 31 | ``` 32 | 33 | As we have multiple `:require` statements we can simply chain them all together as above. 34 | -------------------------------------------------------------------------------- /docs/projects/banking-on-clojure/delete-records.md: -------------------------------------------------------------------------------- 1 | # Delete Records in the database 2 | Using `next.jdbc.sql` functions provides a Clojure data structures approach, where as `next.jdbc/execute!` uses specific SQL statement code. 3 | 4 | {% tabs clojure="next.jdbc.sql functions", sql="next.jdbc/execute!" %} 5 | 6 | {% content "clojure" %} 7 | 8 | ## Generic delete record function 9 | Use the generic delete function from the database schema design section 10 | 11 | ```clojure 12 | (defn delete-record 13 | "Insert a single record into the database using a managed connection. 14 | Arguments: 15 | - table - name of database table to be affected 16 | - record-data - Clojure data representing a new record 17 | - db-spec - database specification to establish a connection" 18 | [db-spec table where-clause] 19 | (with-open [connection (jdbc/get-connection db-spec)] 20 | (jdbc-sql/delete! connection table where-clause))) 21 | ``` 22 | 23 | 24 | ## Delete an existing account_holder record 25 | Call the `delete-record` function with the development database specification, the table name and a where clause to locate the specific record to delete. This where clause should use a unique value, e.g. the primary key for the table. 26 | 27 | ```clojure 28 | (delete-record db-specification-dev :public.account_holders {:account_holder_id "0bed6afe-6740-46a1-b924-36ef192eac66"}) 29 | ``` 30 | 31 | If the record deletion is successful then `:update-count 1` value is returned 32 | 33 | ```clojure 34 | ;; => #:next.jdbc{:update-count 1} 35 | ``` 36 | 37 | ## Deleting an existing account record 38 | Update an existing record in the `public.accounts` table, providing new values for `current_balance` and `last_updated` columns. 39 | 40 | ```clojure 41 | (delete-record db-specification-dev :public.accounts {:account_number "1234567890"}) 42 | ``` 43 | 44 | ## Deleting an existing transaction record 45 | Update an existing record in the `public.transaction_history` table. 46 | 47 | ```clojure 48 | (delete-record db-specification-dev :public.transaction_history {:transaction_id "8ac89cfc-6874-4ebe-9ee4-59b8c5e971ff"}) 49 | ``` 50 | 51 | 52 | {% content "sql" %} 53 | 54 | 55 | ## Insert account_holders 56 | 57 | 58 | ## Insert accounts 59 | 60 | 61 | ## Insert transactions 62 | 63 | 64 | 65 | {% endtabs %} 66 | 67 | 68 | > #### Hint::Generating example data from Clojure Spec 69 | > [Clojure Spec: generate mock database data](clojure-spec-generate-mock-data.md) 70 | -------------------------------------------------------------------------------- /docs/projects/banking-on-clojure/deployment-pipeline.md: -------------------------------------------------------------------------------- 1 | # Deployment Pipeline Approach 2 | Using the Heroku Application platform cloud simplifies the deployment of the Clojure web application. 3 | 4 | ## 12 Factor approach 5 | Following the [12 factor principles](https://12factor.net/), the deployment is driven by source code to multiple environments. 6 | 7 | ![Heroku multiple environment from one source](https://raw.githubusercontent.com/jr0cket/developer-guides/gh-pages/heroku-multiple-environments-concept.png) 8 | 9 | 10 | ## Heroku pipelines 11 | Using Heroku Pipelines the staging environment is promoted to production rather than being rebuilt 12 | 13 | ![Heroku Pipeline concept - staging and production](https://raw.githubusercontent.com/jr0cket/developer-guides/master/heroku-pipelines-staging-production.png) 14 | 15 | The Heroku dashboard can be used to promote the application into production, once the staging application is signed off. 16 | 17 | ![Heroku Pipeline banking-on-clojure](https://raw.githubusercontent.com/practicalli/graphic-design/live/continuous-integration/heroku/heroku-pipeline-banking-on-clojure.png) 18 | 19 | 20 | ## Heroku Build process 21 | The build process starts when commits are pushed to Heroku, either directly or via a continuous integration service (eg. CircleCI). 22 | 23 | [![Heroku build process](https://raw.githubusercontent.com/jr0cket/developer-guides/gh-pages/heroku-deployment-process-simplified.png)](https://raw.githubusercontent.com/jr0cket/developer-guides/gh-pages/heroku-deployment-process-simplified.png) 24 | -------------------------------------------------------------------------------- /docs/projects/banking-on-clojure/generate-data-from-specs.md: -------------------------------------------------------------------------------- 1 | # Generate Data Using Clojure Spec 2 | Test data to populate the database can be generated using the [specifications previously defined using Clojure Spec](spec-generative-testing.md). 3 | -------------------------------------------------------------------------------- /docs/projects/banking-on-clojure/honeysql.md: -------------------------------------------------------------------------------- 1 | # HoneySQL 2 | [HoneySQL](https://github.com/seancorfield/honeysql) is a library for writing SQL as Clojure data structures to programmatically query databases (develop and runtime) without string bashing. 3 | 4 | > #### TODO::work in progress, sorry 5 | -------------------------------------------------------------------------------- /docs/projects/banking-on-clojure/index.md: -------------------------------------------------------------------------------- 1 | # Banking on Clojure web application 2 | 3 | ![Banking on Clojure web application user interface](https://raw.githubusercontent.com/practicalli/graphic-design/live/clojure-web-services/banking-on-clojure-ui-account-overview.png) 4 | 5 | !!! WARNING "Work In Progress" 6 | Project actively being developed as part of the [Practicalli Study group WebApps](https://www.youtube.com/playlist?list=PLpr9V-R8ZxiCe9p9tFk24ChNSpGfanUbT). 7 | 8 | Code so far is shared on [practicalli/banking-on-clojure-webapp](https://github.com/practicalli/banking-on-clojure-webapp) GitHub repository 9 | 10 | Building a Banking application using Clojure, spec, H2 (development) & Postgresql (live) databases and next.jdbc for SQL queries (migratus for db migrations). 11 | 12 | The system infrastructure uses Jetty or HTTP-kit (making this switchable at runtime) and a component life cycle system (probably mount). 13 | 14 | ## Application Design (in progress) 15 | 16 | Data Specifications created using `clojure.spec.alpha` 17 | 18 | 19 | - [x] Customer Details 20 | - [x] Account holder 21 | - Bank account 22 | - Multiple Bank accounts 23 | - Credit Card 24 | - Mortgate 25 | 26 | Functions and function specifications using `clojure.spec.alpha` 27 | 28 | - [x] register-account-holder 29 | - open-credit-account 30 | - open-savings-account 31 | - open-credit-card-account 32 | - open-mortgage-account 33 | - Make a payment 34 | - Send account notification 35 | - Check for overdraft 36 | 37 | Functions with specifications are instrumented to check arguments passed during function calls. 38 | 39 | Generative testing is carried out via the [kaocha test runner](https://practical.li/clojure/testing/test-runners/kaocha-test-runner/){target=_blank} 40 | 41 | 42 | ## Development Workflow 43 | 44 | - [x] Write a failing test 45 | - [x] write mock data 46 | - [x] write an function definition that returns the argument 47 | - [x] run tests - tests should fail 48 | - [x] write a spec for the functions argument - customer 49 | - [x] write a spec for the return value 50 | - write a spec for relationship between args and return value 51 | - [x] replace the mock data with generated values from specification 52 | - [x] update functions and make tests pass 53 | - instrument functions 54 | - run specification checks 55 | -------------------------------------------------------------------------------- /docs/projects/banking-on-clojure/namespace-design.md: -------------------------------------------------------------------------------- 1 | # Namespace design 2 | A common approach to namespace design is to start with the main namespace for the application and migrate code to new namespaces as the codebase grows. 3 | 4 | 5 | ## Basic Principles 6 | Basic principles of namespace design include 7 | 8 | * focus namespaces on specific logical areas of the application 9 | * avoid circular references between namespaces (i.e. two namespaces require each other) 10 | * abstract code into namespaces to avoid the _uber-namespace_ (unless the application fits into ~100 lines of code) 11 | * require the minimum number of namespaces 12 | * use meaningful names for namespace aliases (if naming is hard, think again about splitting a namespace) 13 | * use comment sections to separate code into logical groupings as its developed, highlighting potential sections of code that could split into its own namespace. 14 | 15 | 16 | ## Example web application namespace design 17 | A general design that forms the basis of many web application projects 18 | 19 | ![Clojure WebApps - Banking on Clojure - namespace design](https://raw.githubusercontent.com/practicalli/graphic-design/live/clojure-webapps/banking-on-clojure-design-namespace-segregation.png) 20 | 21 | 22 | ## Main application namespace 23 | The main application namespace is typically used for code that manages the system, for example starting the application server, database, etc. These services are often managed by component lifecycle services (mount, integrant, component). 24 | 25 | Routing is usually a part of the main application namespace, especially when there are a modest number of routes and a routing library such as compojure is used. If routing becomes more extensive, then a separate routing namespace is warranted. 26 | 27 | ## Handlers and custom middleware 28 | Handlers define the business logic, data and presentation that turns requests into responses. Start with a single namespace for handlers and segregate if the complexity grows sufficiently. 29 | 30 | Middleware used directly with handlers is required in the handler namespace. 31 | 32 | Middleware for the overall system may appear in the main application namespace, to wrap the application instance. 33 | 34 | ## UI pages and templates 35 | Avoid adding complexity to the handlers by moving common web page / html generation code to its own namespace. 36 | 37 | A single namespace provides a focused view for refactoring presentation code into templates and generators. 38 | 39 | ## Data queries 40 | A namespace to design all the queries for a data source, which could be database, api's, file systems, etc. 41 | 42 | SQL queries to relational database are defined here. 43 | 44 | ## Data sources 45 | Details of data sources, from databases, api's or any sources of information to be processed. 46 | -------------------------------------------------------------------------------- /docs/projects/banking-on-clojure/read-records.md: -------------------------------------------------------------------------------- 1 | # Read Database Records 2 | Using `next.jdbc.sql` functions provides a Clojure data structures approach, where as `next.jdbc/execute!` uses specific SQL statement code. 3 | 4 | 5 | ## Generic read record function 6 | 7 | Use the generic create function from the database schema design section 8 | 9 | ```clojure 10 | (defn read-record 11 | "Insert a single record into the database using a managed connection. 12 | Arguments: 13 | - table - name of database table to be affected 14 | - record-data - Clojure data representing a new record 15 | - db-spec - database specification to establish a connection" 16 | [db-spec sql-query] 17 | (with-open [connection (jdbc/get-connection db-spec)] 18 | (jdbc-sql/query connection sql-query))) 19 | ``` 20 | 21 | 22 | ## Read account_holder records 23 | 24 | Call the `read-record` function with the development database specification and a Clojure vector containing a string of the SQL select statement. 25 | 26 | Return all the records from a specific table 27 | 28 | ```clojure 29 | (read-record db-specification-dev ["select * from public.account_holders"]) 30 | ``` 31 | 32 | Return records that match a specific where clause 33 | 34 | ```clojure 35 | (read-record db-specification-dev ["select * from public.account_holders where first_name = ?" "Rachel"]) 36 | ``` 37 | 38 | ## Read account records 39 | 40 | Create a new record in the `public.accounts` table. 41 | 42 | Return all the records from a specific table 43 | 44 | ```clojure 45 | (read-record db-specification-dev ["select * from public.accounts"]) 46 | ``` 47 | 48 | Return records that match a specific where clause 49 | 50 | ```clojure 51 | (read-record db-specification-dev ["select * from public.accounts where account_number = ?" "1234567890"]) 52 | ``` 53 | 54 | ## Read transaction history records 55 | 56 | Create a record in the `public.transaction_history` table. 57 | 58 | ```clojure 59 | (read-record db-specification-dev ["select * from public.transaction_history"]) 60 | ``` 61 | 62 | Return records that match a specific where clause 63 | 64 | ```clojure 65 | (read-record db-specification-dev ["select * from public.transaction_history where transaction_date = ?" "2020-09-11"]) 66 | ``` 67 | 68 | !!! HINT "Generating example data from Clojure Spec" 69 | [Clojure Spec: generate mock database data](clojure-spec-generate-mock-data.md){target=_blank} 70 | -------------------------------------------------------------------------------- /docs/projects/banking-on-clojure/refactor-handler.md: -------------------------------------------------------------------------------- 1 | # Refactor to handlers namespace 2 | 3 | Create a new namespace called `practicalli.banking-on-clojure.handler` which will contain handler functions for the routes to be defined in the application. 4 | 5 | Additional libraries will be used to create the responses, which will only be required in the new namespace. 6 | 7 | Create a new file called `src/practicalli/banking_on_clojure/handler.clj` 8 | 9 | Move the `[ring.util.response :refer [response]]` require and the handler function from `src/practicalli/banking_on_clojure/service.clj` to `src/practicalli/banking_on_clojure/handler.clj` 10 | 11 | ```clojure title="src/practicalli/banking_on_clojure/handler.clj" 12 | (ns practicalli.banking-on-clojure.handler 13 | "Handler functions to satisfy requests to the service" 14 | (:require 15 | [ring.util.response :refer [response]])) 16 | ``` 17 | 18 | ```clojure title="src/practicalli/banking_on_clojure/handler.clj" 19 | (defn welcome-page 20 | "Main page layout for the service" 21 | [request] 22 | (response "Banking on Clojure")) 23 | ``` 24 | 25 | 26 | Require the `practicalli.banking-on-clojure.handler` namespace in `practicalli.banking-on-clojure` namespace, using the alias `handler` 27 | 28 | ```clojure title="src/practicalli/banking_on_clojure/service.clj" 29 | (ns practicalli.banking-on-clojure 30 | (:gen-class) 31 | (:require [org.httpkit.server :as app-server] 32 | [compojure.core :refer [defroutes GET]] 33 | [ring.util.response :refer [response]] 34 | [practicalli.handler :as handler])) 35 | ``` 36 | 37 | Update the request routing code to use the new alias for handlers 38 | 39 | ```clojure title="src/practicalli/banking_on_clojure/service.clj" 40 | (defroutes app 41 | (GET "/" [] handler/welcome-page)) 42 | ``` 43 | 44 | Restart the server to pick up the changes 45 | 46 | ```clojure title="src/practicalli/banking_on_clojure/service.clj" 47 | (app-server-restart "8888") 48 | ``` 49 | 50 | Check the server is still working by visiting http://localhost:8888/ 51 | -------------------------------------------------------------------------------- /docs/projects/banking-on-clojure/spec-generative-testing.md: -------------------------------------------------------------------------------- 1 | ![Clojure specifications for next.jdbc](https://raw.githubusercontent.com/practicalli/graphic-design/live/clojure/spec/clojure-spec-blueprints-industrial.png) 2 | 3 | Specifications define the shape of data used for the application. The specifications are defined across two namespaces, general data specifications in `practicalli.specifications` and banking specific specs in the `practicalli.specifications-banking` namespace. 4 | 5 | 6 | Basic customer details 7 | 8 | ```clojure 9 | (spec/def ::first-name string?) 10 | (spec/def ::last-name string?) 11 | (spec/def ::email-address string?) 12 | 13 | ;; residential address values 14 | (spec/def ::house-name-number (spec/or :string string? 15 | :number int?)) 16 | (spec/def ::street-name string?) 17 | (spec/def ::post-code string?) 18 | (spec/def ::county string?) 19 | ``` 20 | 21 | countries of the world as a set, 22 | containing a string for each country 23 | defined in the practicalli.specifications namespace 24 | 25 | ```clojure 26 | (spec/def ::country :practicalli.specifications/countries-of-the-world) 27 | 28 | ``` 29 | 30 | ```clojure 31 | (spec/def ::residential-address (spec/keys :req [::house-name-number ::street-name ::post-code] 32 | :opt [::county ::country])) 33 | 34 | ``` 35 | 36 | 37 | ```clojure 38 | (spec/def ::social-security-id-uk string?) 39 | (spec/def ::social-security-id-usa string?) 40 | 41 | (spec/def ::social-security-id (spec/or ::social-security-id-uk 42 | ::social-security-id-usa)) 43 | ``` 44 | 45 | 46 | ```clojure 47 | ;; composite customer details specification 48 | (spec/def ::customer-details 49 | (spec/keys 50 | :req [::first-name ::last-name ::email-address ::residential-address ::social-security-id])) 51 | ``` 52 | 53 | 54 | ## Banking data specifications 55 | The `specifications-banking` sets the overall context for the specifications defined in the namespace. 56 | 57 | `account-id` is a unique identification across all accounts in the bank. The type of value used is a [universally unique identifier (UUID)](https://en.wikipedia.org/wiki/Universally_unique_identifier) is a 128-bit number used to identify information in computer systems. Clojure uses a [#uuid tag literal](https://clojure.org/reference/reader#tagged_literals) 58 | 59 | ```clojure 60 | (spec/def ::account-id uuid?) 61 | 62 | ;; Account holder - composite specification 63 | (spec/def ::account-holder 64 | (spec/keys 65 | :req [::account-id 66 | ::first-name 67 | ::last-name 68 | ::email-address 69 | ::residential-address 70 | ::social-security-id])) 71 | ``` 72 | -------------------------------------------------------------------------------- /docs/projects/banking-on-clojure/unit-testing-the-database.md: -------------------------------------------------------------------------------- 1 | # Unit Testing with the Database 2 | 3 | 4 | ## Unit testing using clojure spec 5 | 6 | Require the clojure.spec namespaces 7 | * `[clojure.spec.alpha :as spec]` for the core spec functions, including `gen` for specification generators 8 | * `[clojure.spec.gen.alpha :as spec-gen]` for generate and sample functions to generate values from specifications 9 | * `[clojure.spec.test.alpha :as spec-test]` for running check on instrumented function definitions 10 | * `[practicalli.specifications-banking]` for the banking related specifications 11 | 12 | ```clojure 13 | (ns practicalli.database-access-test 14 | (:require 15 | ;; Unit testing 16 | [clojure.test :refer [deftest is testing]] 17 | 18 | ;; Clojure Specifications 19 | [clojure.spec.alpha :as spec] 20 | [clojure.spec.test.alpha :as spec-test] 21 | [clojure.spec.gen.alpha :as spec-gen] 22 | [practicalli.specifications-banking] 23 | 24 | ;; System under test 25 | [practicalli.database-access :as SUT]) 26 | ) 27 | ``` 28 | 29 | A simpler test to check that a map of generated values is returned when calling `new-account-holder` 30 | 31 | ```clojure 32 | (deftest new-account-holder-test 33 | (testing "Registered account holder is valid specification" 34 | (is (map? (SUT/new-account-holder 35 | (spec-gen/generate 36 | (spec/gen :practicalli.specifications-banking/customer-details))))))) 37 | ``` 38 | 39 | > This test alone will fail in a CI environment as the application does not create the database tables automatically 40 | 41 | 42 | ## In a continuous integration environment 43 | 44 | A Continuous Integration environment will be empty to start with, so when using an embedded database the database and database tables need to be created each time the tests run. 45 | 46 | Creating the tables each time the tests are run could lead to errors if the database already has those tables defined 47 | 48 | ![Table exists](/images/clojure-webapps-database-create-table-when-exists-error.png) 49 | 50 | 51 | Add `IF NOT EXISTS` to the `CREATE TABLE` SQL statement so that the `create-tables!` function returns nil rather than an SQL error. 52 | 53 | ```clojure 54 | (def schema-account-holders-table 55 | ["CREATE TABLE IF NOT EXISTS PUBLIC.ACCOUNT_HOLDERS( 56 | ACCOUNT_HOLDER_ID UUID DEFAULT RANDOM_UUID() NOT NULL, 57 | FIRST_NAME VARCHAR(32), 58 | LAST_NAME VARCHAR(32), 59 | EMAIL_ADDRESS VARCHAR(32) NOT NULL, 60 | RESIDENTIAL_ADDRESS VARCHAR(255), 61 | SOCIAL_SECURITY_NUMBER VARCHAR(32), 62 | CONSTRAINT ACCOUNT_HOLDERS_PK PRIMARY KEY (ACCOUNT_HOLDER_ID))"]) 63 | ``` 64 | 65 | 66 | 67 | 68 | 69 | > Longer term: Run migratus scripts to establish the database schema each time. 70 | -------------------------------------------------------------------------------- /docs/projects/banking-on-clojure/update-records.md: -------------------------------------------------------------------------------- 1 | # Update Records in the database 2 | 3 | Several options were explored when designing database query functions. Using next.jdbc.sql functions provides a Clojure data structures approach, where as `next.jdbc/execute!` uses specific SQL statement code. 4 | 5 | Take the SQL approach if generating SQL statements directly. 6 | 7 | Take the Clojure approach if to generate SQL statements from Clojure data structures. 8 | 9 | 10 | ## Generic update record function 11 | 12 | Use the generic create function from the database schema design section 13 | 14 | ```clojure 15 | (defn update-record 16 | "Insert a single record into the database using a managed connection. 17 | Arguments: 18 | - table - name of database table to be affected 19 | - record-data - Clojure data representing a new record 20 | - db-spec - database specification to establish a connection 21 | - where-clause - column and value to identify a record to update" 22 | [db-spec table record-data where-clause] 23 | (with-open [connection (jdbc/get-connection db-spec)] 24 | (jdbc-sql/update! connection table record-data where-clause))) 25 | 26 | ``` 27 | 28 | 29 | ## Update an existing account_holder record 30 | 31 | Call the `update-record` function with the development database specification, the account holder table name and a Clojure hash-map of the record data. 32 | 33 | Each key in the map represents a column name and the value associated with the key is the value to be inserted in the record for its column. 34 | 35 | ```clojure 36 | (update-record db-specification-dev 37 | :public.account_holders 38 | {:EMAIL_ADDRESS "rachel+update@rockketpack.org"} 39 | {:account_holder_id "f6d6c3ba-c5cc-49de-8c85-21904f8c5b4d"}) 40 | ``` 41 | 42 | If the update is successful then `:update-count 1` value is returned 43 | 44 | ```clojure 45 | ;; => #:next.jdbc{:update-count 1} 46 | ``` 47 | 48 | ## Update an existing account record 49 | 50 | Update an existing record in the `public.accounts` table, providing new values for `current_balance` and `last_updated` columns. 51 | 52 | ```clojure 53 | (update-record db-specification-dev 54 | "public.accounts" 55 | {:current_balance 242 56 | :last_updated "2020-09-12"} 57 | {:account_number "1234567890"}) 58 | ``` 59 | 60 | ## Update an existing transaction record 61 | 62 | Update an existing record in the `public.transaction_history` table. 63 | 64 | ```clojure 65 | (update-record db-specification-dev 66 | "public.transaction_history" 67 | {:transaction_reference "Salary bonus" 68 | :transaction_date "2020-09-12"} 69 | {:transaction_id "8ac89cfc-6874-4ebe-9ee4-59b8c5e971ff"}) 70 | ``` 71 | 72 | 73 | !!! HINT "Generating example data from Clojure Spec" 74 | [Clojure Spec: generate mock database data](clojure-spec-generate-mock-data.md){target=_blank} 75 | -------------------------------------------------------------------------------- /docs/projects/game-scoreboard-api/index.md: -------------------------------------------------------------------------------- 1 | # Game Scoreboard API 2 | 3 | Building an API from scratch using 4 | 5 | * Reitit 6 | * muuntaja 7 | * reitit-ring middleware 8 | * Integrant REPL - reloaded workflow 9 | * Integrant - runtime component lifecycle 10 | * mulog - event logging 11 | * Auth0 - authentication and authorisation 12 | -------------------------------------------------------------------------------- /docs/projects/index.md: -------------------------------------------------------------------------------- 1 | # Project Guides 2 | 3 | Follow a step-by-step project guide and build a live services which can also be deployed. 4 | -------------------------------------------------------------------------------- /docs/projects/leiningen/todo-app/compojure/.md: -------------------------------------------------------------------------------- 1 | # 2 | -------------------------------------------------------------------------------- /docs/projects/leiningen/todo-app/compojure/about.md: -------------------------------------------------------------------------------- 1 | # About route 2 | 3 | > ####Note:: Write an about route and handler that gives you information about the app. 4 | > 5 | ```clojure 6 | (defn about 7 | "Information about the website developer" 8 | [request] 9 | {:status 200 10 | :headers {} 11 | :body "I am an awesome Clojure developer, well getting there..."}) 12 | ``` 13 | > 14 | > Add a route to call the about handler function 15 | ```clojure 16 | (defroutes app 17 | (GET "/" [] welcome) 18 | (GET "/goodbye" [] goodbye) 19 | (GET "/about" [] about) 20 | (not-found "

This is not the page you are looking for

21 |

Sorry, the page you requested was not found!

")) 22 | ``` 23 | -------------------------------------------------------------------------------- /docs/projects/leiningen/todo-app/compojure/adding-dependency.md: -------------------------------------------------------------------------------- 1 | # Add Compojure as a dependency 2 | 3 | Edit your project configuration `project.clj` and add the [current version of Compojure](https://clojars.org/compojure) 4 | 5 | The `project.clj` file should look as follows: 6 | 7 | ```clojure 8 | (defproject todo-list "0.1.0-SNAPSHOT" 9 | :description "A Todo List server-side webapp using Ring & Compojure" 10 | :url "https://github.com/practicalli/clojure-todo-list-example" 11 | :license {:name "Creative Commons Attribution Share-Alike 4.0 International" 12 | :url "https://creativecommons.org"} 13 | :dependencies [[org.clojure/clojure "1.10.1"] 14 | [ring "1.8.0"] 15 | [compojure "1.6.1"]] 16 | :repl-options {:init-ns todo-list.core} 17 | :main todo-list.core 18 | :profiles {:dev 19 | {:main todo-list.core/-dev-main}}) 20 | ``` 21 | 22 | As we are adding a library to the project we need to restart the web server. 23 | 24 | `Ctrl-c` in the terminal to stop the server and `lein run 8000` to restart the web server 25 | 26 | 27 | > ####Hint::Search clojars.org for dependency versions 28 | > The [current version of Compojure](https://clojars.org/compojure) or any other Clojure library can be found via [Clojars.org](https://clojars.org/). 29 | -------------------------------------------------------------------------------- /docs/projects/leiningen/todo-app/compojure/adding-goodbye-route.md: -------------------------------------------------------------------------------- 1 | # Adding a goodbye route 2 | 3 | > ####Note::Add another route to display a goodbye message 4 | > 5 | ```clojure 6 | (defroutes app 7 | (GET "/" [] welcome) 8 | (GET "/goodbye" [] goodbye) 9 | (not-found "Sorry, page not found")) 10 | ``` 11 | 12 | 13 | > ####Note::Write the handler function for the `goodbye` route 14 | > 15 | ```clojure 16 | (defn goodbye 17 | "A song to wish you goodbye" 18 | [request] 19 | {:status 200 20 | :headers {} 21 | :body "

Walking back to happiness

22 |

Walking back to happiness with you

23 |

Said, Farewell to loneliness I knew

24 |

Laid aside foolish pride

25 |

Learnt the truth from tears I cried

"}) 26 | ``` 27 | > 28 | > Now [test your new route](http://localhost:8000/goodbye). 29 | > 30 | > As we have `wrap-reload` around app then no restart needed 31 | -------------------------------------------------------------------------------- /docs/projects/leiningen/todo-app/compojure/code-so-far.md: -------------------------------------------------------------------------------- 1 | # Code so far 2 | 3 | The code and configuration we have created so far are in the [clojure-todo-list-example repository](https://github.com/practicalli/clojure-todo-list-example) github repository. 4 | 5 | Code for this section is in the branch called `04-compojure` 6 | 7 | If something is not working or you want to speed up, simply clone the project into a new directory using the command: 8 | 9 | ```bash 10 | git clone https://github.com/practicalli/clojure-todo-list-example 11 | ``` 12 | Once you have cloned the project, checkout the `04-compojure` branch 13 | 14 | ```bash 15 | git checkout 04-compojure 16 | ``` 17 | -------------------------------------------------------------------------------- /docs/projects/leiningen/todo-app/compojure/defroutes.md: -------------------------------------------------------------------------------- 1 | # Theory: defroutes is a Clojure macro 2 | 3 | The Compojure function `defroutes` is actually a Clojure macro. The `defroutes` macro provides a simple syntax for defining routes and associating handler functions. 4 | 5 | ## What is a macro? 6 | 7 | Clojure has a programmatic macro system which allows the Clojure community to extend the language, rather than wait for the language designers. This macro approach also helps keep the language very compact, with a minimum of primitives. 8 | 9 | We have already used several macros in our code. In our project.clj configuration we use the `defproject` macro to make it easy to define our Clojure project. In our code we have used the `defn` macro to define names (symbols) for functions. 10 | 11 | ## Peeking under the covers 12 | 13 | You can always look at what a macro is doing by using the `macroexpand` or `macroexpand-all` functions. These functions show you what the code looks like after the macro-reader has processed the macro. 14 | 15 | To expand a macro, require the `clojure.walk` library in your namespace 16 | 17 | ```clojure 18 | [clojure.walk :as walk] 19 | ``` 20 | 21 | Then wrap the macro you wish to explore with the `macroexpand-all` function. 22 | 23 | ```clojure 24 | (walk/macroexpand-all 25 | '(defroutes myapp 26 | (GET "/" [] "Show something"))) 27 | 28 | 29 | (def myapp 30 | (compojure.core/routes 31 | (compojure.core/make-route 32 | :get #clout.core.CompiledRoute{:source "/", :re #"/", :keys [], :absolute? false} 33 | (fn* ([request__13075__auto__] (let* [] "Show something")))))) 34 | ``` 35 | 36 | You can see that the `defroutes` function expands to a `make-route` function that creates the details of the route and associates it with a handler or response map. 37 | The `routes` function join multiple routes together. 38 | 39 | For further examples, see http://learnxinyminutes.com/docs/clojure-macros/ 40 | -------------------------------------------------------------------------------- /docs/projects/leiningen/todo-app/compojure/index.md: -------------------------------------------------------------------------------- 1 | # Compojure 2 | 3 | To make our webapp more useful we will add more functionality, which will require more routes. 4 | 5 | [Compojure](https://github.com/weavejester/compojure) is a library that works with Ring to manage 6 | 7 | * **routing** - running different code depending on the URL path received 8 | * **http method switching** - running different code based on the HTTP method (GET, POST, PUT, DELETE) 9 | 10 | Compojure also has convenience functions that make ring responses easier to generate. 11 | 12 | In this section we will update our project to use Compojure. 13 | 14 | ![Ring - Compojure routes](../images/clojure-ring-adaptor-middleware-route--handler-overview.png) 15 | 16 | 17 | ## Leiningen Templates 18 | 19 | Templates can be used to create a project with a given set of dependencies as well as Clojure code. 20 | 21 | There is a `compojure` template that gives you a basic running web application. To use this template to create a new project use the following command, substituting your own project-name 22 | 23 | ```bash 24 | lein new compojure project-name 25 | ``` 26 | 27 | This project contains ring and compojure. The dependency for ring is `ring/site-defaults` which includes some sensible default settings for your application, eg security settings such as anti-forgery. 28 | 29 | See the definition of [ring/site-defaults](https://github.com/ring-clojure/ring-defaults/blob/master/src/ring/middleware/defaults.clj) for further information. 30 | 31 | 32 | ## Resources 33 | 34 | * [Learn X in minutes - Compojure](http://learnxinyminutes.com/docs/compojure/) - using httpkit (performance & scalability) 35 | * [Webapps with Compojure & OM](http://zaiste.net/2014/02/web_applications_in_clojure_all_the_way_with_compojure_and_om/) 36 | * [StackOverflow - What's the “big idea” behind compojure routes?](http://stackoverflow.com/questions/3488353/whats-the-big-idea-behind-compojure-routes) 37 | -------------------------------------------------------------------------------- /docs/projects/leiningen/todo-app/compojure/lisp-calculator.md: -------------------------------------------------------------------------------- 1 | # Lisp style Calculator 2 | 3 | Lets create a very simple lisp based calculator that works with two numbers as another example of using variable path elements. As its a Lisp calculator, then we will use prefix notation (the 'operator' comes first) 4 | 5 | ## Create a route for the calculator 6 | 7 | ```clojure 8 | (defroutes app 9 | (GET "/" [] greet) 10 | (GET "/goodbye" [] goodbye) 11 | (GET "/about" [] about) 12 | (GET "/request-info" [] handle-dump) 13 | (GET "/hello/:name" [] hello) 14 | (GET "/calculator/:op/:a/:b" [] calculator) 15 | (not-found "Sorry, page not found")) 16 | ``` 17 | 18 | 19 | ## Create a handler function to add, subtract, divide or multiply two numbers 20 | 21 | ```clojure 22 | (defn calculator 23 | "A very simple calculator that can add, divide, subtract and multiply. This is done through the magic of variable path elements." 24 | [request] 25 | (let [a (Integer. (get-in request [:route-params :a])) 26 | b (Integer. (get-in request [:route-params :b])) 27 | op (get-in request [:route-params :op]) 28 | f (get operands op)] 29 | (if f 30 | {:status 200 31 | :body (str "Calculated result: " (f a b)) 32 | :headers {}} 33 | {:status 404 34 | :body "Sorry, unknown operator. I only recognise + - * : (: is for division)" 35 | :headers {}}))) 36 | ``` 37 | 38 | ## Create a dictionary to look up Clojure function names 39 | 40 | Define a hash-map called `operands` to look up the names of the mathematical operations (operands) to the actual functions in Clojure 41 | 42 | ```clojure 43 | (def operands {"+" + "-" - "*" * ":" /}) 44 | ``` 45 | 46 | Try the calculator out like follows http://localhost:8000/calculator/*/6/7 47 | 48 | 49 | ## The namespace with changes made 50 | 51 | With all the changes from above, the code should look as follows 52 | 53 | ```clojure 54 | (def operands {"+" + "-" - "*" * ":" /}) 55 | 56 | (defn calculator 57 | "A very simple calculator that can add, divide, subtract and multiply. This is done through the magic of variable path elements." 58 | [request] 59 | (let [a (Integer. (get-in request [:route-params :a])) 60 | b (Integer. (get-in request [:route-params :b])) 61 | op (get-in request [:route-params :op]) 62 | f (get operands op)] 63 | (if f 64 | {:status 200 65 | :body (str (f a b)) 66 | :headers {}} 67 | {:status 404 68 | :body "Sorry, unknown operator. I only recognise + - * : (: is for division)" 69 | :headers {}}))) 70 | 71 | (defroutes app 72 | (GET "/" [] welcome) 73 | (GET "/goodbye" [] goodbye) 74 | (GET "/about" [] about) 75 | (GET "/request-info" [] handle-dump) 76 | (GET "/yo/:name" [] yo) 77 | (GET "/calculator/:op/:a/:b" [] calculator) 78 | (not-found "Sorry, page not found")) 79 | ``` 80 | -------------------------------------------------------------------------------- /docs/projects/leiningen/todo-app/compojure/show-request-info.md: -------------------------------------------------------------------------------- 1 | # Show request info 2 | 3 | We can see the details of the requests being send to our Clojure webapp by looking at the request object. 4 | 5 | ## request-info route 6 | 7 | Add a `request-info` route and handler to view the request information 8 | 9 | ```clojure 10 | (defn request-info 11 | "View the information contained in the request, useful for debugging" 12 | [request] 13 | {:status 200 14 | :body (pr-str request) 15 | :headers {}}) 16 | 17 | (defroutes app 18 | (GET "/" [] welcome) 19 | (GET "/goodbye" [] goodbye) 20 | (GET "/about" [] about) 21 | (GET "/request-info" [] request-info) 22 | (not-found "

This is not the page you are looking for

Sorry, the page you requested was not found!

")) 23 | ``` 24 | 25 | Visit http://localhost:8000/request-info to see the results. 26 | 27 | ![Output of the request](/images/clojure-webdev-request-info-pr-str-output.png) 28 | 29 | 30 | ## Using Compojure request dump function 31 | 32 | Compojure has a request dump function that gives a much nicer output than our initial `request-info` function. The `dump` function also separates the default response keys with any additional keys provided by the URL. 33 | 34 | ## Include `handle-dump` in the namespace 35 | 36 | ```clojure 37 | (ns webdev.core 38 | (:require [ring.adapter.jetty :as jetty] 39 | [ring.middleware.reload :refer [wrap-reload]] 40 | [compojure.core :refer [defroutes GET]] 41 | [compojure.route :refer [not-found]] 42 | [ring.handler.dump :refer [handle-dump]])) 43 | ``` 44 | 45 | ## Remove request-info function 46 | 47 | Delete the `request-info` function we defined previously and update the `/request-info` route to use `handle-dump` as the handler 48 | 49 | ```clojure 50 | (defroutes app 51 | (GET "/" [] welcome) 52 | (GET "/goodbye" [] goodbye) 53 | (GET "/about" [] about) 54 | (GET "/request-info" [] handle-dump) 55 | (not-found "

This is not the page you are looking for

Sorry, the page you requested was not found!

")) 56 | ``` 57 | 58 | Now the output is much nicer http://localhost:8000/request-info 59 | 60 | ![Clojure Web Services - compojure request-dump output](/images/clojure-webdev-compojure-request-dump-output.png) 61 | -------------------------------------------------------------------------------- /docs/projects/leiningen/todo-app/compojure/theory-local-name-bindings.md: -------------------------------------------------------------------------------- 1 | ## Theory: Local binding with `let` 2 | The `let` function binds a name to a value within the scope of the `let` function. The name is used to represent the value it is bound to, especially useful if the value is complex or the result of an expression. 3 | 4 | ```clojure 5 | (let [name value]) 6 | ``` 7 | 8 | Binding values to names can be used to remove duplicate code, making the code more efficient. 9 | 10 | ## Binding any value 11 | A let expression can bind a name to any Clojure value, from a simple number or string, to a collection or result of an expression. 12 | 13 | In our example we are pulling out a value from a map and using the `let` function to create a name we can use to reference that value. The name is used to in the body of the response map, so when the response map is returned the page is displayed with the name. 14 | 15 | ```clojure 16 | (let [name (get-in request [:route-params :name])] 17 | {:status 200 18 | :body (str "Hello " name ". I got your name from the web URL") 19 | :headers {}}) 20 | ``` 21 | 22 | The Ring adaptor creates a Clojure hash-map from the browser request which is called the request map. The request map is passed to handler functions. 23 | 24 | 25 | ## A binding is immediately available 26 | A name is available for use as soon as it is bound, even within the name/value bindings section of the `let` expression. 27 | 28 | ```eval-clojure 29 | (let [apples 10 30 | oranges 15 31 | total-fruit (+ apples oranges)] 32 | (str "Total fruit: " total-fruit)) 33 | ``` 34 | 35 | 36 | > #### Hint::Use meaningful names or avoid local names 37 | > Use meaningful names in let expressions to effectively communicate the purpose of the code. 38 | > 39 | > If it is hard to find a meaningful name, either the problem space is not understood enough or local names may not be necessary. 40 | -------------------------------------------------------------------------------- /docs/projects/leiningen/todo-app/compojure/theory-routing.md: -------------------------------------------------------------------------------- 1 | # Theory: routing 2 | 3 | In compojure, each route is a combination offer a HTTP method paired with a URL-matching pattern, an argument list, and a handler. The handler is typically the name of function. 4 | 5 | ```clojure 6 | (defroutes myapp 7 | (GET "/" [] show-something) 8 | (POST "/" [] create-something) 9 | (PUT "/" [] replace-something) 10 | (PATCH "/" [] modify-something) 11 | (DELETE "/" [] annihilate-something) 12 | (OPTIONS "/" [] appease-something) 13 | (HEAD "/" [] preview-something)) 14 | ``` 15 | 16 | A handler is a functions which accept request maps and return response maps. 17 | 18 | ```clojure 19 | (defn show-something 20 | "A simple handler function" 21 | [request] 22 | {:status 200 23 | :headers {"Content-Type" "text/html; charset=utf-8} 24 | :body "

I am a simple handler function

"}) 25 | ``` 26 | 27 | These handler functions can be called by passing a Clojure hash-map. The result is another Clojure hash-map that contains values for `:status`, `:headers` and `:body`. 28 | 29 | ```clojure 30 | (show-something {:uri "/" :request-method :post}) 31 | ;; => {:status 200 32 | ;; :headers {"Content-Type" "text/html; charset=utf-8} 33 | ;; :body "

I am a simple handler function

"} 34 | ``` 35 | 36 | The body may be a function, which must accept the request as a parameter: 37 | 38 | ```clojure 39 | (defroutes myapp 40 | (GET "/" [] (fn [req] "Do something with req"))) 41 | ``` 42 | 43 | Or, you can just use the request directly: 44 | 45 | ```clojure 46 | (defroutes myapp 47 | (GET "/" req "Do something with req")) 48 | ``` 49 | 50 | Route patterns may include named parameters: 51 | 52 | ```clojure 53 | (defroutes myapp 54 | (GET "/hello/:name" [name] (str "Hello " name))) 55 | ``` 56 | 57 | You can adjust what each parameter matches by supplying a regex: 58 | 59 | ```clojure 60 | (defroutes myapp 61 | (GET ["/file/:name.:ext" :name #".*", :ext #".*"] [name ext] 62 | (str "File: " name ext))) 63 | ``` 64 | -------------------------------------------------------------------------------- /docs/projects/leiningen/todo-app/compojure/theory-using-hash-maps.md: -------------------------------------------------------------------------------- 1 | # Theory: Accessing hash-maps 2 | The request is a Clojure hash-map made up of key / value pairs, referred to as the request map. The keys are Clojure keywords. The values are typically strings or Clojure collections (vectors, hash-maps). 3 | 4 | Here is an example of a request map 5 | 6 | ```clojure 7 | {:request-params {:name "John"}} 8 | ``` 9 | 10 | Using the `get` function to return the value for a particular keyword in the request-map 11 | 12 | ```clojure 13 | (get request-map :keyword) 14 | ``` 15 | 16 | ## Using hash-map as a function 17 | A hash-map can be evaluated as a function call to the map with the key as an argument. Any type of key can be used in this expression. 18 | 19 | ```clojure 20 | (request-map :keyword) 21 | (request-map "key as string") 22 | ``` 23 | 24 | ## Nested hash-maps 25 | Two `get` expressions could be used to return a particular value when accessing a nested hash-map. The inner get expression returns a hash-map and the outer get expression returns the value. 26 | 27 | ```clojure 28 | (get (get outer-map :outer-keyword) :inner-keyword) 29 | ``` 30 | 31 | With many nested maps, the `get` function can lead to code that is harder to read. Using the `get-in` function provides a simpler syntax for traversing nested maps 32 | 33 | `get-in` walks through the nested hash-map along the path defined by the vector of keys. 34 | 35 | ```clojure 36 | (get-in request-map [:outer-keyword :inner-keyword]) 37 | ``` 38 | 39 | 40 | ## Using keywords and hash-maps 41 | Keywords can be evaluated as a function call with a hash-map as an argument and return their associated value in that hash-map. 42 | 43 | ```clojure 44 | (def response-map {:name "john" :path "/hello"} 45 | ``` 46 | 47 | You can get the value from this map using the keyword 48 | 49 | ```clojure 50 | (response-map :name) 51 | 52 | => "john" 53 | 54 | (response-map :path) 55 | 56 | => "/hello" 57 | ``` 58 | 59 | Other types of keys do not work as function calls. Either use the map as a function with the key as an argument or use the `get` and `get-in` functions as appropriate. 60 | -------------------------------------------------------------------------------- /docs/projects/leiningen/todo-app/compojure/using-compojure.md: -------------------------------------------------------------------------------- 1 | # Using Compojure in the project 2 | 3 | The Compojure `defroute` function provides a syntax for defining routes and associating handlers. 4 | 5 | ![Ring - adding defroutes to manage routes](../images/clojure-ring-adaptor-middleware-route--handler-wrap-reload.png) 6 | 7 | 8 | ## Add Compojure to the namespace 9 | 10 | Add the `defroutes` function, `GET` protocol and `notfound` route from Compojure to the namespace 11 | 12 | ```clojure 13 | (ns todo-list.core 14 | (:require [ring.adapter.jetty :as jetty] 15 | [ring.middleware.reload :refer [wrap-reload]] 16 | [compojure.core :refer [defroutes GET]] 17 | [compojure.route :refer [not-found]])) 18 | ``` 19 | 20 | ## Refactor the welcome function to just say Hello 21 | 22 | The welcome function should just do one simple thing, return a welcome message. 23 | 24 | ```clojure 25 | (defn welcome 26 | "A ring handler to respond with a simple welcome message" 27 | [request] 28 | {:status 200 29 | :body "

Hello, Clojure World

30 |

Welcome to your first Clojure app, I now update automatically

" 31 |

I now use defroutes to manage incoming requests

32 | :headers {}}) 33 | ``` 34 | 35 | 36 | ## Add a defroutes function 37 | 38 | Add a `defroutes` function called `app` to manage our routes. Add routes for `/` and send all other requests to the Compojure `not-found` function. 39 | 40 | ```clojure 41 | (defroutes app 42 | (GET "/" [] welcome) 43 | (not-found "

This is not the page you are looking for

44 |

Sorry, the page you requested was not found!

")) 45 | ``` 46 | 47 | 48 | ## Update -dev-main and -main functions 49 | 50 | Change the `-dev-main` and `-main` functions to call the `app` function, instead of the `welcome` function 51 | 52 | ```clojure 53 | (defn -main 54 | "A very simple web server using Ring & Jetty" 55 | [port-number] 56 | (webserver/run-jetty app 57 | {:port (Integer. port-number)})) 58 | 59 | (defn -dev-main 60 | "A very simple web server using Ring & Jetty that reloads code changes via the development profile of Leiningen" 61 | [port-number] 62 | (webserver/run-jetty (wrap-reload #'app) 63 | {:port (Integer. port-number)})) 64 | ``` 65 | 66 | As we have changed the `-dev-main` and `-main` functions, we need to restart the server again - `Ctrl-c` then `lein run 8000` 67 | 68 | Now test out your updated web app by visiting http://localhost:8000 and http://localhost:8000/not-there 69 | -------------------------------------------------------------------------------- /docs/projects/leiningen/todo-app/compojure/variable-path-elements.md: -------------------------------------------------------------------------------- 1 | # Variable Path Elements 2 | 3 | A simple way to affect the behaviour of a web app is to add extra text (elements) to the web address (URL). For example, you can add your name to the end of the web address and the returned web page will include your name. 4 | 5 | By adding an element to the route path, we can take that element from the URL as it is part of the request. We can then get that value from the request map and use it in our body content. 6 | 7 | ## Hello handler example 8 | 9 | Create a simple personalised hello message by adding a route for `/hello` with `/:name` as a path element. 10 | 11 | Create a `hello` function as the handler that pulls out the `:name` element from the request and adds it to the response. 12 | 13 | ```clojure 14 | (defn hello 15 | "A simple personalised greeting showing the use of variable path elements" 16 | [request] 17 | (let [name (get-in request [:route-params :name])] 18 | {:status 200 19 | :body (str "Hello " name ". I got your name from the web URL") 20 | :headers {}})) 21 | 22 | (defroutes app 23 | (GET "/" [] greet) 24 | (GET "/goodbye" [] goodbye) 25 | (GET "/about" [] about) 26 | (GET "/request-info" [] handle-dump) 27 | (GET "/hello/:name" [] hello) 28 | (not-found "Sorry, page not found")) 29 | ``` 30 | 31 | Now you can test this route out by also including a name to the URL path http://localhost:8000/hello/john 32 | -------------------------------------------------------------------------------- /docs/projects/leiningen/todo-app/connect-to-postgres/add-database-dependencies.md: -------------------------------------------------------------------------------- 1 | # Add Dependency 2 | 3 | Our application will use JDBC (Java database connectivity) to connect to the Postgres database. So we need to add the JDBC library along with a a specific JDBC driver for Postgres. 4 | 5 | > ####Note:: Add Dependencies to the project for the Heroku Postgres database 6 | 7 | Edit the project configuration file, `project.clj` and add the following dependencies 8 | 9 | ```clojure 10 | [org.clojure/java.jdbc "0.7.10"] 11 | [org.postgresql/postgresql "42.2.9"] 12 | ``` 13 | 14 | The `project.clj` file should now look as follows: 15 | 16 | ```clojure 17 | (defproject todo-list "0.1.0-SNAPSHOT" 18 | :description "A Todo List server-side webapp using Ring & Compojure" 19 | :url "https://github.com/practicalli/clojure-todo-list-example" 20 | :license {:name "Creative Commons Attribution Share-Alike 4.0 International" 21 | :url "https://creativecommons.org"} 22 | 23 | :dependencies [[org.clojure/clojure "1.10.1"] 24 | [ring "1.8.0"] 25 | [compojure "1.6.1"] 26 | [org.clojure/java.jdbc "0.7.10"] 27 | [org.postgresql/postgresql "42.2.9"]] 28 | :min-lein-version "2.0.0" 29 | :repl-options {:init-ns todo-list.core} 30 | :main todo-list.core 31 | :profiles {:dev 32 | {:main todo-list.core/-dev-main} 33 | :uberjar {:aot :all}} 34 | :uberjar-name "todo-list.jar" 35 | :auto-clean false) 36 | ``` 37 | -------------------------------------------------------------------------------- /docs/projects/leiningen/todo-app/connect-to-postgres/index.md: -------------------------------------------------------------------------------- 1 | # Connecting to Heroku PostgreSQL from Clojure 2 | 3 | * Add dependencies 4 | * Define a database connection (Heroku posgres) 5 | * Migrations (TODO) 6 | 7 | 8 | ## Using JDBC for Relational Databases 9 | Java Database connectivity is a common way to connect to a relational database and has very widespread database support. 10 | 11 | [next.jdbc](https://github.com/seancorfield/next-jdbc) is a Clojure library to send SQL statements over jdbc or use a DSL such as [HoneySQL](https://github.com/seancorfield/honeysql)) to work with these databases. 12 | -------------------------------------------------------------------------------- /docs/projects/leiningen/todo-app/create-a-handler-function/add-not-found.md: -------------------------------------------------------------------------------- 1 | # Add Error message when request not found 2 | 3 | So far our app has responded with the same message, regardless of the web address (route) requested in the browser. The webapp will be more useful if it responds differently to different routes. 4 | 5 | > ####Note::Add error message to handler 6 | > Change the code to only respond with content when requesting the default route, that is http://localhost:8000/. Anything else we will return an error. 7 | > 8 | > Edit the `welcome` function in `src/todo-list/core.clj` and use an `if` function to check if the request is valid or not. 9 | 10 | ```clojure 11 | (defn welcome 12 | "A ring handler to process all requests for the web server. 13 | If a request is for something other than `/` then an error message is returned" 14 | [request] 15 | (if (= "/" (:uri request)) 16 | {:status 200 17 | :body "

Hello, Clojure World

18 |

Welcome to your first Clojure app.

" 19 | :headers {}} 20 | {:status 404 21 | :body "

This is not the page you are looking for

22 |

Sorry, the page you requested was not found!>

" 23 | :headers {}})) 24 | ``` 25 | 26 | If the route matches `/` then a response map with the welcome message is returned. For any other route, a response map containing our error message is returned. 27 | 28 | ## Run the new version of your code 29 | 30 | If your server is still running, kill it first using `Ctrl-c` keyboard shortcut. Then run the server again, this time with the new code using the same command as before: 31 | 32 | ```bash 33 | lein run 8000 34 | ``` 35 | 36 | Open http://localhost:8000 in your browser and try out different pages, such at [/hello]( http://localhost:8000/hello), [/goodbye]( http://localhost:8000/goodbye) or [/complete-indifference]( http://localhost:8000/complete-indifference). 37 | 38 | Only http://localhost:8000 will return the welcome message, everything else should return the error message. 39 | 40 | ![todo-list - bad route error message](../images/todo-list-not-the-page-you-are-looking-for.png) 41 | -------------------------------------------------------------------------------- /docs/projects/leiningen/todo-app/create-a-handler-function/code-so-far.md: -------------------------------------------------------------------------------- 1 | # Code so far 2 | 3 | The code and configuration we have created so far are in the [clojure-webapps-example](https://github.com/practicalli/clojure-webapps-example) github repository. 4 | 5 | Code for this section is in the branch called `02-create-a-handler-function` 6 | 7 | If something is not working or you want to speed up, simply clone the project into a new directory using the command: 8 | 9 | ```bash 10 | git clone https://github.com/practicalli/clojure-webapps-example 11 | ``` 12 | Once you have cloned the project, checkout the `02-create-a-handler-function` branch 13 | 14 | ```bash 15 | git checkout 02-create-a-handler-function 16 | ``` 17 | -------------------------------------------------------------------------------- /docs/projects/leiningen/todo-app/create-a-handler-function/if-function.md: -------------------------------------------------------------------------------- 1 | # Theory: if function 2 | 3 | Clojure has an `if` function that evaluates an expresssion. If that expression is true, then the first value is returned, if false then the second argument is returned. 4 | 5 | In pseudo-code, the `if` function in Clojure works as follows 6 | 7 | ```basic 8 | If (this expression is true ?) 9 | then return this value 10 | else return this value 11 | ``` 12 | 13 | In the project code an `if` function checks the web address by returning the value associated with `:uri` in the request map. 14 | 15 | If the `:url` value is equal to `/` then the first response map with the hello message is returned. 16 | 17 | If the `:uri` value is not equal to `/` then the second resource map with an error message is returned. 18 | 19 | 20 | ```clojure 21 | (if (= "/" (:uri request)) 22 | {:status 200 23 | :body "

Hello, Clojure World

24 |

Welcome to your first Clojure app.

" 25 | :headers {}} 26 | {:status 404 27 | :body "

This is not the page you are looking for

28 |

Sorry, the page you requested was not found!>

" 29 | :headers {}})) 30 | ``` 31 | 32 | 33 | > ####Hint::Single path if function 34 | > In the case where an if expression is defined with only one value and the expression is false, then the value `nil` is returned. `when` function is the idiomatic choice over a single path `if` function. 35 | 36 | 37 | ## Multiple expression if function with do 38 | Each of the two possible values the `if` function returns can come from only evaluating one expression. For example 39 | 40 | ```clojure 41 | (if (true) 42 | (str "I am the truth") 43 | (str "I am the path to darkness") 44 | ``` 45 | 46 | If you need multiple expressions they can be wrapped in the `do` function 47 | 48 | ```clojure 49 | (if (true) 50 | (do (some-function) 51 | (another-function)) 52 | (else-function)) 53 | ``` 54 | 55 | The `do` function calls each function evaluation in turn, returning the result of the last function called. 56 | 57 | > ####Hint::Compojure for managing routes 58 | > The `if` function is a very simplistic way to define routes in our web application. Compojure is a library for elegantly managing routing for our Clojure server-side applications. 59 | -------------------------------------------------------------------------------- /docs/projects/leiningen/todo-app/create-a-handler-function/index.md: -------------------------------------------------------------------------------- 1 | # Create a handler function 2 | So far we have just sent back the same response map. To make our webapp more useful then we should have functions that return different web pages and resources (like JSON for API's). 3 | 4 | In Ring terminology, these functions are referred to as a handler. They handler a request and return a response. 5 | 6 | When you send a request to the webapp, the ring adaptor converts this request to a map and sends it to the specified handler. 7 | 8 | ![Ring - Adaptor and Handler](../images/ring-basics-adaptor-handler-request-response.png) 9 | 10 | A handler function takes the request map as its argument and returns a response map. 11 | 12 | > ####Note::Add separate handler function 13 | > Refactor the code in the `src/todo-list/core.clj` file to create a separate `welcome` handler function that processes all requests 14 | > 15 | ```clojure 16 | (defn welcome 17 | "A ring handler to process all requests sent to the webapp" 18 | [request] 19 | {:status 200 20 | :headers {} 21 | :body "

Hello, Clojure World

22 |

Welcome to your first Clojure app. 23 | This message is returned regardless of the request, sorry

"}) 24 | ``` 25 | > 26 | > Update the `-main` function to call the `welcome` function 27 | > 28 | ```clojure 29 | (defn -main 30 | "A very simple web server using Ring & Jetty" 31 | [port-number] 32 | (webserver/run-jetty 33 | welcome 34 | {:port (Integer. port-number) 35 | :join? false})) 36 | ``` 37 | 38 | 39 | 40 | ## Run the server again 41 | 42 | Save code changes and run the web server (use Control-c if you need to stop the server first) 43 | 44 | ```bash 45 | lein run 8000 46 | ``` 47 | 48 | Your webapp should behave exactly as it did before, check by visiting http://localhost:8000. 49 | 50 | ![Clojure webapp - running todo-list project](/images/todo-list-lein-run-portnumber.png) 51 | 52 | > #### Hint::Automatically reloading 53 | > In the middleware section the `wrap-reload` ring middleware component is used to automatically reload code changes into the running application, so no need to restart the webserver unless we have to add a dependency. 54 | -------------------------------------------------------------------------------- /docs/projects/leiningen/todo-app/create-a-handler-function/maps-and-keywords.md: -------------------------------------------------------------------------------- 1 | ## Theory: maps and keywords 2 | 3 | When a request is received by our application, it is converted from by Jetty to a servlet request. Ring then converts this to a Clojure map called `request`. All handlers in our application take a request map as an argument. 4 | 5 | A map in Clojure contains one or more key / value pairs, you may be familiar with the term _hash map_. The keys in these maps are often defined using a Clojure _keyword_. A keyword is a symbol that points to itself and so therefore is unique within a specific scope. A keyword makes it very easy to get a value from a map and acts as a function on the map to return its associated value 6 | 7 | So, assume we have defined a map called `request`. This map contains a key defined with the `:uri` keyword. We can get the value associated with the key using the keyword as a function 8 | 9 | ```clojure 10 | (def request {:uri "/"}) 11 | 12 | (:uri request) 13 | 14 | ;; As a map can also act as a function to get its elements, you can also use the following form to get the same value 15 | (request :uri) 16 | ``` 17 | 18 | The function `get` is functions that helps us get data from maps. The function `get-in` helps us get data from nested levels of maps. 19 | -------------------------------------------------------------------------------- /docs/projects/leiningen/todo-app/create-a-project/code-so-far.md: -------------------------------------------------------------------------------- 1 | # The code so far 2 | 3 | The code and configuration we have created so far are in the [clojure-todo-list-example repository](https://github.com/practicalli/clojure-todo-list-example) github repository. 4 | 5 | Code for this section is in the branch called `master` 6 | 7 | If something is not working or you want to speed up, simply clone the project into a new directory using the command: 8 | 9 | ```bash 10 | git clone https://github.com/practicalli/clojure-todo-list-example 11 | ``` 12 | Once you have cloned the project, checkout the `master` branch 13 | 14 | ```bash 15 | git checkout master 16 | ``` 17 | -------------------------------------------------------------------------------- /docs/projects/leiningen/todo-app/create-a-project/index.md: -------------------------------------------------------------------------------- 1 | # Create a project 2 | 3 | Create a project called `todo-list` using Leiningen, the build automation tool for Clojure. This project will run the simplest possible webserver. 4 | 5 | On the command line: 6 | 7 | ```bash 8 | lein new todo-list 9 | ``` 10 | 11 | ![Leiningen - new project called todo-list](../images/lein-new-todo-list.png) 12 | 13 | 14 | ## Take a look at the project structure 15 | 16 | Change into the `todo-list` directory created by the Leiningen command and see the project structure that has been created. 17 | 18 | * `project.clj` - the project configuration, written in Clojure 19 | * `src` for all the source code 20 | * `test` for unit test code 21 | 22 | Using the `tree` command is a simple way to see the project structure (alternatively use `ls -R` or a graphical file browser). 23 | 24 | ![Clojure project structure - webdev](/images/project-todo-list-tree.png) 25 | 26 | 27 | > ####Hint:: File names and the Java class path 28 | > The `src` and `test` directories both contain a directory named `todo_list` even though our project is `todo-list`. 29 | > 30 | > Unfortunately the Java classpath does not like dashes '-' in directory or file names, so Leiningen changes the directory names to `src/todo_list` & `test/todo_list` and the initial test to `src/todo_list/core_test.clj`. 31 | -------------------------------------------------------------------------------- /docs/projects/leiningen/todo-app/create-a-project/update-project-details.md: -------------------------------------------------------------------------------- 1 | # Update project details 2 | 3 | Adding project details to the `project.clj` file helps every developer that works with the code to have a basic understanding of the projects purpose. 4 | 5 | > #### Note::Update project details 6 | > Edit the `project.clj` file and make the following changes. 7 | > 8 | * Add a description 9 | * Add the URL of the project, eg. the github repository 10 | * Update the licence (optional) 11 | * Update the dependencies to the latest Clojure version 12 | > 13 | > 14 | > The project.clj file for Practicalli projects is as follows: 15 | > 16 | ```clojure 17 | (defproject todo-list "0.1.0-SNAPSHOT" 18 | :description "A Todo List server-side webapp using Ring & Compojure" 19 | :url "https://github.com/practicalli/clojure-todo-list-example" 20 | :license {:name "Creative Commons Attribution Share-Alike 4.0 International" 21 | :url "https://creativecommons.org"} 22 | :dependencies [[org.clojure/clojure "1.10.1"]] 23 | :repl-options {:init-ns todo-list.core}) 24 | ``` 25 | 26 | ## Licence change 27 | 28 | All code by Practicalli is under the Creative Commons Attribution Share-alike. 29 | 30 | As well as changing the `project.clj` file `:licence` declaration, the `LICENCE` file created by the Leiningen template has been deleted as it refers to another licence. 31 | -------------------------------------------------------------------------------- /docs/projects/leiningen/todo-app/create-a-webserver-with-ring/add-a-jetty-webserver.md: -------------------------------------------------------------------------------- 1 | # Run Jetty web server 2 | 3 | The Ring Jetty adaptor is used to run an instance of Jetty. The `-main` function contains an anonymous function that takes any request and returns a _response map_. 4 | 5 | The `-main` function takes a port number as an argument which we pass when running the application. 6 | 7 | ![Ring - Adaptor and anonymous function](../images/clojure-ring-adaptor-anonymous-function.png) 8 | 9 | A response map contains the following key / value pairs 10 | * `:status` - the result of the request, eg. 200 OK, 401 Not Found, etc 11 | * `:body` - the content to be returned (web page, json, etc) 12 | * `:headers` - a map of standard headers included in any web browser response 13 | 14 | 15 | Add a function called `-main` to the `src/todo_list/core.clj` file. 16 | 17 | ```clojure 18 | (defn -main 19 | "A very simple web server using Ring & Jetty" 20 | [port-number] 21 | (webserver/run-jetty 22 | (fn [request] 23 | {:status 200 24 | :headers {} 25 | :body "

Hello, Clojure World

26 |

Welcome to your first Clojure app. 27 | This message is returned regardless of the request, sorry

"}) 28 | {:port (Integer. port-number) 29 | :join? false})) 30 | ``` 31 | 32 | ## Explaining the new function 33 | 34 | Using a `-` at the start of the `-main` function is a naming convention, helping you see which function is the entry point to your program. Leiningen also looks for this -main function by default when running your application. 35 | 36 | The `webserver/run-jetty` function takes two arguments. In our example, the first argument is an anonymous function that returns a map (the response to the browser request); the second argument is a port number to run the jetty server on expressed as a Java Integer object. 37 | 38 | The `Integer.` function is a call to `java.lang.Integer`. The `.` is a special form that tells Clojure to treat this name as a call to Java. See [coercing types and java.lang](coersing-types-and-java-lang.html) 39 | 40 | The `:join? false` setting enables the REPL prompt to run after the web server starts. By default the join setting is true and the running server would block access to the REPL prompt. 41 | -------------------------------------------------------------------------------- /docs/projects/leiningen/todo-app/create-a-webserver-with-ring/add-ring-dependency.md: -------------------------------------------------------------------------------- 1 | # Add Ring Dependency 2 | Add the ring library as a dependency of the todo-list project. 3 | 4 | > #### Note::Add ring dependency 5 | > Edit the `project.clj` file and add **[ring "1.8.0"]** to the `:dependencies` section, after the Clojure library dependency. 6 | > 7 | ```clojure 8 | (defproject todo-list "0.1.0-SNAPSHOT" 9 | :description "A Todo List server-side webapp using Ring & Compojure" 10 | :url "https://github.com/practicalli/clojure-todo-list-example" 11 | :license {:name "Creative Commons Attribution Share-Alike 4.0 International" 12 | :url "https://creativecommons.org"} 13 | :dependencies [[org.clojure/clojure "1.10.1"] 14 | [ring "1.8.0"]] 15 | :repl-options {:init-ns todo-list.core}) 16 | ``` 17 | 18 | --- 19 | 20 | > ####Hint::Dependencies with Leiningen 21 | Read the [dependencies section of the Leiningen documentation](https://github.com/technomancy/leiningen/blob/stable/doc/TUTORIAL.md#dependencies) to learn more about adding libraries. 22 | 23 | ## Looking up Libraries & current versions 24 | Libraries created by the Clojure community can be found on [Clojars.org](https://clojars.org), an online repository similar to Maven Central. 25 | 26 | Use the Clojars.org website to search for the [latest version of Ring](https://clojars.org/search?q=ring). 27 | 28 | ![Clojars.org ring dependency](/images/clojure-webdev-clojars-ring.png) 29 | 30 | The dependency notation for Leiningen is documented for each library, making it easy to add the library to your project. 31 | -------------------------------------------------------------------------------- /docs/projects/leiningen/todo-app/create-a-webserver-with-ring/code-so-far.md: -------------------------------------------------------------------------------- 1 | # The code so far 2 | 3 | The code created so far is in the [clojure-webapps-example](https://github.com/practicalli/clojure-webapps-example) github repository, specifically the branch called `01-create-a-webserver` 4 | 5 | If something is not working or you want to speed up, simply clone the project (if you have not already done so) into a new directory using the command: 6 | 7 | ```bash 8 | git clone https://github.com/practicalli/clojure-webapps-example 9 | ``` 10 | 11 | Checkout the `01-create-a-webserver` branch to see the relevant version of the code 12 | 13 | ```bash 14 | git checkout 01-create-a-webserver-with-ring 15 | ``` 16 | -------------------------------------------------------------------------------- /docs/projects/leiningen/todo-app/create-a-webserver-with-ring/coersing-types-and-java-lang.md: -------------------------------------------------------------------------------- 1 | # Theory: Specifying Types & java.lang 2 | 3 | Clojure has types that are created dynamically when the code is compiled, with everything being represented by Java objects as its compiled to Java byte code. 4 | 5 | Clojure simply infers the type of a value, so types do not need to be specified in code. 6 | 7 | The built in collections (list, map, vector & set) also support mixed types too. 8 | 9 | ## Calling Java code 10 | 11 | The Clojure project uses Jetty, a web application server written in Java. When calling the `run-jetty` function an Integer type must be passed to the Java object for the port number. 12 | 13 | When running the Clojure project, the argument supplied for the port number on the command line is treated as a String object. Therefore we need to explicitly cast the port number from a Java String type to an Java Integer type. 14 | 15 | ## java.lang library 16 | 17 | The `java.lang.` library is part of all Clojure projects, so as we are going to create a Java Integer it makes sense to simply use the `Integer` constructor with a String argument which returns a new Integer object. 18 | 19 | `(Integer. port-number)` calls the `java.lang.Integer` constructor. 20 | 21 | The `.` is actually a macro in Clojure that provides a simple way to work with Java, allowing you to call Java objects as if they were Clojure functions. In Java you would have to use the form `Type instance-name = new Type(argument)`. In our example you would write this in Java as `String port = new String(port-number)` 22 | 23 | From the [Java 8 docs for Integer class](https://docs.oracle.com/javase/8/docs/api/java/lang/Integer.html): `Integer(String s)` - constructs a newly allocated Integer object that represents the int value indicated by the String parameter. 24 | 25 | 26 | ## Theory: Its Java Objects underneath strings & numbers 27 | 28 | Strings and numbers are represented by Java objects underneath, so its convenient to use Java Classes to manipulate these simple data structures on the rare occasion you need a specific type. 29 | 30 | You can see the underlying Java types in Clojure using the `type` or `class` function. In the following example you can see the Java types for strings and numbers 31 | 32 | ![Clojure Types: String to Integer examples](/images/clojure-types-examples-string-to-integer.png) 33 | -------------------------------------------------------------------------------- /docs/projects/leiningen/todo-app/create-a-webserver-with-ring/configure-main-namespace.md: -------------------------------------------------------------------------------- 1 | # Configure main namespace 2 | 3 | Setting the default namespace will automatically call a function called `-main` when the Clojure project is run, i.e. via `lein run` 4 | 5 | > #### Note::Add main namespace 6 | >Edit the `project.clj` file and add `:main todo-list.core` configuration option. 7 | > 8 | ```clojure 9 | (defproject todo-list "0.1.0-SNAPSHOT" 10 | :description "A Todo List server-side webapp using Ring & Compojure" 11 | :url "https://github.com/practicalli/clojure-todo-list-example" 12 | :license {:name "Creative Commons Attribution Share-Alike 4.0 International" 13 | :url "https://creativecommons.org"} 14 | :dependencies [[org.clojure/clojure "1.10.1"] 15 | [ring "1.8.0"]] 16 | :repl-options {:init-ns todo-list.core} 17 | :main todo-list.core) 18 | ``` 19 | -------------------------------------------------------------------------------- /docs/projects/leiningen/todo-app/create-a-webserver-with-ring/include-ring-library.md: -------------------------------------------------------------------------------- 1 | # Including Ring in the Namespace 2 | 3 | Add the `ring-adaptor-jetty` namespace from the ring library, so we can use the functions from that library. 4 | 5 | > ####NOTE::Require the ring-adaptor 6 | > Delete all code in `src/todo_list/core.clj` and replace it with the following code. 7 | > 8 | ```clojure 9 | (ns todo-list.core 10 | (:require [ring.adapter.jetty :as webserver])) 11 | ``` 12 | 13 | The `ns` expression defines the current namespace as `todo-list.core`, providing a scope for all the functions and data structures we define within it. 14 | 15 | The `:require` expression makes the `ring.adaptor.jetty` namespace accessible within the `todo-list.core` namespace. We can now call any of the public functions in the `ring.adaptor.jetty` namespace. 16 | 17 | In `ring.adapter.jetty` namespace is bound to the `webserver` alias, providing a short name to refer to functions from that namespace. 18 | 19 | For example, the `run-jetty` function is called using `webserver/run-jetty` rather than the fully qualified namespace of `ring.adaptor.jetty/run-jetty` 20 | 21 | > ####Hint::Using aliases for namespaces 22 | > Using `:require` we can use the `:as` keyword to specify an alias for a namespace, a short-hand way of referring to a library. You can specify any valid Clojure name for a namespace alias, however please consider the readability of your code and choose a meaningful alias name. 23 | > 24 | > Later in the workshop we will show other options for including functions from other namespaces. 25 | -------------------------------------------------------------------------------- /docs/projects/leiningen/todo-app/create-a-webserver-with-ring/index.md: -------------------------------------------------------------------------------- 1 | # Use the Ring Library to create a webserver 2 | 3 | The Ring library can start an embedded Java server (eg. Jetty, Tomcat) to listen for requests from a browser. Each browser request received is converted into a request map, a Clojure map with keys and values. This request map is passed to a handler function, which returns a response map. 4 | 5 | ![Ring - Adaptor and anonymous function](../images/clojure-ring-adaptor-anonymous-function.png) 6 | 7 | In the section you will discover how to: 8 | 9 | * [Add the Ring library as a dependency](add-ring-dependency.html) 10 | * [Including Ring in the namespace](include-ring-library.html) 11 | * [Add a main function to run a Jetty webserver](add-a-jetty-webserver.html) 12 | * [Configure the project's main namespace](configure-main-namespace.html) 13 | * [Run webserver](run-webserver.html) 14 | 15 | 16 | ## Related Theory 17 | 18 | We will cover some related theory on [Coercing types](coersing-types-and-java-lang.md) (also known as casting types) to help us deal with Java interoperability. 19 | 20 | We will also cover how to manage the scope of your Clojure code with [Namespaces](namespaces.html). 21 | 22 | > ####Hint::Ring details 23 | > [Ring is covered in more detail](../introducing-ring/index.md) in the next section, once you have your first webserver up and running. 24 | -------------------------------------------------------------------------------- /docs/projects/leiningen/todo-app/create-a-webserver-with-ring/namespaces.md: -------------------------------------------------------------------------------- 1 | # Theory: Namespaces 2 | A namespace in Clojure is used to manage the logical separation of code, usually along features of the application. A namespace limits the scope of functions and names of data structures to a specific namespace. 3 | 4 | The names bound to function definitions using the `defn` function can be used elsewhere in the namespace just by using the name. The same goes for any values bound to a name using the `def` function. 5 | 6 | > ####Hint::Clojure order of evaluation 7 | > The code in a Clojure namespace is evaluated only once and from top to bottom. To call a named function or data structure, it must have its definition evaluated first. 8 | 9 | To use a function outside the namespace, you need to use its namespace and its name, for example `clojure.string/reverse` 10 | 11 | ## Include another namespace in the REPL 12 | 13 | The `require` function will provide access functions and names from specific namespaces and an alias for the namespace can also be specified with the `:as` directive. 14 | 15 | A function from that namespace can then be used by prefixing its name with the alias specified in the `require` expression. 16 | 17 | Here is an example of including the `clojure.string` namespace and calling its `reverse` function 18 | 19 | ```clojure 20 | (require '[clojure.string :as string]) 21 | 22 | (string/reverse "RedRum") 23 | ``` 24 | 25 | 26 | ## Including another namespace in source code 27 | 28 | Instead of the `require` function, add the `:require` keyword in the namespace definition, `ns`. 29 | 30 | ```clojure 31 | (ns todo-list.core 32 | (:require '[clojure.string :as string]) 33 | 34 | (string/reverse "RedRum") 35 | ``` 36 | 37 | If a function will be used many times in the namespace, you can `:refer` a function so you can call it just by name, as if it had been defined in the current namespace. 38 | 39 | ```clojure 40 | (ns todo-list.core 41 | (:require '[clojure.string :refer [reverse]])) 42 | 43 | (reverse "RedRum") 44 | ``` 45 | 46 | > ####Hint::Dependency conflicts - avoid the `use` function 47 | > The `use` function and `:use` within an `ns` definition are seen as a bad practice and should be avoided. 48 | > 49 | > The `use` function includes all the functions as if they had been written in the including a great many unused functions into the namespace. It will also pull in all the other namespace functions that each namespace included. 50 | > 51 | > As Clojure is typically composed of many libraries, its prudent to only include the specific things you need from another namespace. This also helps reduce conflicts when including multiple libraries in your project. 52 | 53 | 54 | ## Namespaces outside the project 55 | To use a namespace from a library that is not part of the project, you also need to include it as a dependency. We saw in [add ring dependency](add-ring-dependency.html) how to add a library as a `:dependency` in the Leiningen `project.clj` file. 56 | -------------------------------------------------------------------------------- /docs/projects/leiningen/todo-app/create-a-webserver-with-ring/run-webserver.md: -------------------------------------------------------------------------------- 1 | # Run webserver 2 | 3 | Run the webserver we use Leiningen, the Clojure build automation tool. 4 | 5 | ## Run the webserver 6 | 7 | In a command line terminal, navigate to the root of your project and type the following command 8 | 9 | ```bash 10 | lein run 8000 11 | ``` 12 | 13 | This command will start an embedded Jetty web server that listens on http://localhost:8000. 14 | 15 | ![Todo list project - lein run with port number](../images/todo-list-lein-run-portnumber.png) 16 | 17 | Open http://localhost:8000 in your browser and try out different pages, such at [http://localhost:8000/hello]( http://localhost:8000/hello), [/goodbye]( http://localhost:8000/goodbye) or [/makes-no-difference]( http://localhost:8000/makes-no-difference). It should not matter what page you visit, you should get the same response. 18 | 19 | ![todo-list project in the browser](../images/todo-list-lein-run-browser.png) 20 | 21 | To stop the server, press **Control-c** in the terminal used to run the lein command. 22 | -------------------------------------------------------------------------------- /docs/projects/leiningen/todo-app/database-model/alternative-approaches.md: -------------------------------------------------------------------------------- 1 | # Alternative approaches 2 | 3 | > ####Hint:: Here is an alterative approach to the code just created, for comparison purposes only. There is no need to implement any of the following code (unless you prefer this approach) 4 | 5 | 6 | 7 | ## Using UUID-OSSP Postgres plugin 8 | 9 | The UUID-OSSP extension to our Heroku postgres database to autogenerate universal ID's (UUID). These UUID's are managed by postgres and therefore not resistant to braking from code. The database memory overhead for UUID's is typically less than using text based ID's 10 | 11 | ```clojure 12 | (defn create-table [db] 13 | (db/execute! 14 | db 15 | ["CREATE EXTENSION IF NOT EXISTS \"UUID-OSSP\"" ]) 16 | (db/execute! 17 | db 18 | ["CREATE TABLE IF NOT EXISTS items 19 | (id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), 20 | name TEXT NOT NULL, 21 | description BOOLEAN NOT NULL DEFAULT FALSE, 22 | date_created TIMESTAMPTZ NOT NULL DEFAULT now()"])) 23 | ``` 24 | 25 | > **Fixme** What is the clojure.java.jdbc version of the above ? 26 | 27 | 28 | ## Add more database functions 29 | 30 | 31 | ```clojure 32 | (defn create-item [db name description] 33 | (:id (first (db/query 34 | db ["INSERT INTO items (name, description) 35 | VALUES (?, ?) 36 | RETURN id" 37 | name 38 | description])))) 39 | 40 | (defn update-item [db id checked] 41 | (= [1] (db/execute! 42 | db 43 | ["UPDATE items 44 | SET checked = ? 45 | WHERE id = ?" 46 | checked 47 | id]))) 48 | 49 | (defn delete-item [db id] 50 | (= [1] (db/execute! 51 | db 52 | ["DELETE FROM items 53 | WHERE id = ?" 54 | id]))) 55 | 56 | (defn read-items [db] 57 | (db/query 58 | db 59 | ["SELECT id, name, description, checked, date_created 60 | FROM items 61 | ORDER BY date_created"])) 62 | ``` 63 | -------------------------------------------------------------------------------- /docs/projects/leiningen/todo-app/database-model/create-table.md: -------------------------------------------------------------------------------- 1 | # Create table 2 | 3 | > #### Hint::Recommend using next.jdbc 4 | > next.jdbc is the next generation of clojure.java.jdbc and is recommended instead. The API is very similar, although with many improvements 5 | 6 | We have our database model for tasks, so lets create write some code that will create a database table in Postgres, assuming that table is not there already. 7 | 8 | 9 | ## Create items namespace 10 | 11 | Create a new Clojure file `src/todo_list/items.clj` and add the following code 12 | 13 | First add a dependency for Clojure.java.jdbc 14 | 15 | ```clojure 16 | [clojure.java.jdbc :as sql] 17 | ``` 18 | 19 | You `items.clj` should look like 20 | 21 | ```clojure 22 | (ns todo-list.items 23 | (:require [clojure.java.jdbc :as sql])) 24 | ``` 25 | 26 | We only want to create the database if it does not already exist, so we can check if the table is already part of the schema 27 | 28 | ```clojure 29 | (defn db-schema-migrated? 30 | "Check if the schema has been migrated to the database" 31 | [] 32 | (-> (sql/query postgres 33 | [(str "select count(*) from information_schema.tables " 34 | "where table_name='tasks'")]) 35 | first :count pos?)) 36 | ``` 37 | 38 | Then add a condition to check if the table exists and if not then create the database table 39 | 40 | ```clojure 41 | (defn apply-schema-migration 42 | "Apply the schema to the database" 43 | [] 44 | (when (not (db-schema-migrated?)) 45 | (sql/db-do-commands postgres 46 | (sql/create-table-ddl 47 | :tasks 48 | [:id :serial "PRIMARY KEY"] 49 | [:body :varchar "NOT NULL"] 50 | [:created_at :timestamp 51 | "NOT NULL" "DEFAULT CURRENT_TIMESTAMP"])))) 52 | ``` 53 | 54 | 55 | ## What Heroku does when you create a database 56 | 57 | Heroku Postgres users are granted all non-superuser permissions on their database. These include `SELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER, CREATE, CONNECT, TEMPORARY, EXECUTE,` and `USAGE`. 58 | 59 | Heroku runs the SQL below to create a user and database for you. 60 | 61 | You cannot create or modify databases and roles on Heroku Postgres. The SQL below is for reference only. 62 | 63 | ```sql 64 | CREATE ROLE user_name; 65 | ALTER ROLE user_name WITH LOGIN PASSWORD 'password' NOSUPERUSER NOCREATEDB NOCREATEROLE; 66 | CREATE DATABASE database_name OWNER user_name; 67 | REVOKE ALL ON DATABASE database_name FROM PUBLIC; 68 | GRANT CONNECT ON DATABASE database_name TO database_user; 69 | GRANT ALL ON DATABASE database_name TO database_user; 70 | ``` 71 | -------------------------------------------------------------------------------- /docs/projects/leiningen/todo-app/database-model/create-task.md: -------------------------------------------------------------------------------- 1 | # Create a task 2 | 3 | Write a function to create tasks in the database 4 | 5 | ```clojure 6 | (defn create-task [task-name] 7 | (sql/insert! postgres 8 | :tasks [:body] [task-name]) 9 | (println task-name)) 10 | ``` 11 | -------------------------------------------------------------------------------- /docs/projects/leiningen/todo-app/database-model/delete-task.md: -------------------------------------------------------------------------------- 1 | # Delete task 2 | -------------------------------------------------------------------------------- /docs/projects/leiningen/todo-app/database-model/index.md: -------------------------------------------------------------------------------- 1 | # Creating a database model 2 | 3 | > #### Hint::TODO: Complete the database model 4 | > The examples in Database model section are not finished, although hopefully you have learned enough to be able to continue working on this for homework. 5 | > 6 | > Please ask questions and share your approaches in the [Practicalli Contact channels](https://practicalli.github.io/#contact) 7 | > 8 | > These examples will be updated to use next.jdbc over Winter 2020 9 | 10 | 11 | Our tasks are quite simple and so its easy to represent them as a single table 12 | 13 | * id (auto-generated) 14 | * name of task 15 | * description of task 16 | * type of task 17 | 18 | Each task will have a unique ID, automatically generated when a new record is created. 19 | 20 | The name, description and type of task are all strings. 21 | 22 | > ####Hint:: The type of task could be managed by a second table that lists all the tasks. However, this is only meant to be a simple app at this stage. 23 | 24 | 25 | 26 | ## Namespace design 27 | 28 | We need to decide what namespace to put our data model in. It seems to make sense to create a new namespace, to help keep our code clean and to separate concerns. So we will create a namespace `todo-list.list` namespace. 29 | 30 | We will need to decide whether to add the `items` namespace to core or to the handlers... or maybe create another handler namespace for handlers that just access the database 31 | 32 | ```clojure 33 | (:require [todo-list.items :as items] 34 | ``` 35 | -------------------------------------------------------------------------------- /docs/projects/leiningen/todo-app/database-model/show-all-task.md: -------------------------------------------------------------------------------- 1 | # Show all tasks 2 | 3 | Write a function to list all the tasks in the database, limited to the first 128 items 4 | 5 | 6 | ```clojure 7 | (defn all-tasks [] 8 | (into [] (sql/query postgres ["select * from tasks order by id desc limit 128"]))) 9 | ``` 10 | 11 | -------------------------------------------------------------------------------- /docs/projects/leiningen/todo-app/heroku/code-so-far.md: -------------------------------------------------------------------------------- 1 | # Code so far 2 | 3 | The code and configuration we have created so far are in the [clojure-todo-list-example repository](https://github.com/practicalli/clojure-todo-list-example) github repository, 4 | 5 | Code for this section is in the branch called `` 6 | 7 | If something is not working or you want to speed up, simply clone the project into a new directory using the command: 8 | 9 | ```bash 10 | git clone https://github.com/practicalli/clojure-todo-list-example 11 | ``` 12 | Once you have cloned the project, checkout the `` branch 13 | 14 | ```bash 15 | git checkout 16 | ``` 17 | -------------------------------------------------------------------------------- /docs/projects/leiningen/todo-app/heroku/deploy.md: -------------------------------------------------------------------------------- 1 | # Deploy to Heroku 2 | 3 | First we need to create an Heroku app to deploy our Clojure webapp to. This adds a remote repository we can push our code to using Git. 4 | 5 | > ####Note:: Create an Heroku app using the Heroku dashboard or using the following Heroku toolbelt command 6 | 7 | In a command line terminal, navigate to the root of your project (where your `project.clj` file is) 8 | 9 | ```bash 10 | heroku create 11 | ``` 12 | 13 | If we have changes in our source code files, then we should first add and then commit them to our local repository. 14 | 15 | ```bash 16 | git add . 17 | git commit -m "meaningful commit message" 18 | ``` 19 | 20 | Now push the Clojure webapp code to Heroku and wait a few moments for it to deploy 21 | 22 | ```bash 23 | git push heroku master 24 | ``` 25 | -------------------------------------------------------------------------------- /docs/projects/leiningen/todo-app/heroku/index.md: -------------------------------------------------------------------------------- 1 | # Deploying to Heroku 2 | 3 | Heroku is a developer-focused Platform as a Service, using the tools developers know well. You can simply push your projects to Heroku using Git and your application is deployed for you automatically. 4 | 5 | * Create a [free Heroku account](https://heroku.com) 6 | * Download the [Heroku Toolbelt](https://toolbelt.heroku.com) 7 | 8 | ## Identify your laptop to Heroku 9 | 10 | To be able to deploy your app to Heroku, you first need establish a trusted connection between your laptop and Heroku. Run the following command (from the Heroku Toolbelt) 11 | 12 | ```bash 13 | heroku login 14 | ``` 15 | 16 | Enter your username and password for Heroku. Also enter your 2-factor authorisation code if you enabled that on your Heroku account. 17 | 18 | Credentials are cached so `heroku login` should only need to be run once per computer and user account. 19 | -------------------------------------------------------------------------------- /docs/projects/leiningen/todo-app/heroku/update-project.md: -------------------------------------------------------------------------------- 1 | # Update the project 2 | 3 | Specify how Leiningen builds the project in more detail and tell Heroku how to run the application. 4 | 5 | 6 | ## Configure the Leiningen Build 7 | 8 | Update the Clojure project file with a minimum version number for Leiningen and a name for the jar file that Leiningen will build 9 | 10 | Edit the `project.clj` file and add the following lines, usually after the dependencies declarations 11 | 12 | ```clojure 13 | :min-lein-version "2.0.0" 14 | :uberjar-name "todo-list.jar" 15 | ``` 16 | 17 | The update project file should look as follows 18 | 19 | ```clojure 20 | (defproject todo-list "0.1.0-SNAPSHOT" 21 | :description "A simple webapp using Ring" 22 | :url "http://example.com/FIXME" 23 | :license {:name "Eclipse Public License" 24 | :url "http://www.eclipse.org/legal/epl-v10.html"} 25 | :dependencies [[org.clojure/clojure "1.6.0"] 26 | [ring "1.4.0-beta2"] 27 | [compojure "1.3.4"]] 28 | :main todo-list.core 29 | :min-lein-version "2.0.0" 30 | :uberjar-name "todo-list.jar" 31 | :profiles {:dev 32 | {:main todo-list.core/-dev-main}}) 33 | ``` 34 | -------------------------------------------------------------------------------- /docs/projects/leiningen/todo-app/hiccup/code-so-far.md: -------------------------------------------------------------------------------- 1 | # Code so far 2 | 3 | -------------------------------------------------------------------------------- /docs/projects/leiningen/todo-app/hiccup/create-new-handler.md: -------------------------------------------------------------------------------- 1 | # Create a new handler 2 | 3 | 4 | ```clojure 5 | (defn trying-hiccup 6 | [request] 7 | (html5 {:lang "en"} 8 | [:head (include-js "myscript.js") (include-css "mystyle.css")] 9 | [:body 10 | [:div [:h1 {:class "info"} "This is Hiccup"]] 11 | [:div [:p "Take a look at the HTML generated in this page, compared to the about page"]] 12 | [:div [:p "Style-wise there is no difference between the pages as we haven't added anything in the stylesheet, however the hiccup page generates a more complete page in terms of HTML"]]])) 13 | ``` 14 | 15 | > ####Hint::Named content sections 16 | > As content grows, refactor it into `def` expressions to give content sections names. Pages can use names in the handler code that represent the content, simplifying the handler code. 17 | > 18 | > As the project grows, break code into a view namespace with layouts and specific views defined in their own namespace. 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /docs/projects/leiningen/todo-app/hiccup/index.md: -------------------------------------------------------------------------------- 1 | # Hiccup - HTML Library 2 | 3 | Hiccup is a library for generating HTML from Clojure, keeping your code consistent and easier to write and maintain. 4 | 5 | Using HTML, a heading would be written as: 6 | 7 | ```html 8 |

I am a heading

9 | ``` 10 | 11 | Hiccup uses vectors to define HTML tags and maps to represent styles and other attributes. So the same heading in Hiccup would be written as: 12 | 13 | ```clojure 14 | [:h1 {:class "heading"} "I am a heading"] 15 | ``` 16 | 17 | 18 | 19 | ## Using Hiccup 20 | 21 | Add the hiccup dependency to your `project.clj` file 22 | 23 | ```clojure 24 | [hiccup "1.0.5"] 25 | ``` 26 | 27 | In the REPL, require the hiccup.core library 28 | 29 | ```clojure 30 | user=> (require '[hiccup.core :as markup]) 31 | ;; => nil 32 | ``` 33 | 34 | Or add hiccup to the namespace definition in your Clojure code file. 35 | 36 | ```clojure 37 | (ns my-namespace.core 38 | (require '[hiccup.core :as markup])) 39 | ``` 40 | 41 | 42 | 43 | ## Writing Hiccup 44 | 45 | Here is a basic example of Hiccup syntax: 46 | 47 | ```clojure 48 | (markup/html [:span {:class "foo"} "bar"]) 49 | ;; => "bar" 50 | ``` 51 | 52 | The first element of the vector is used as the element name. The second attribute can optionally be a map, in which case it is used to supply the element's attributes. Every other element is considered part of the tag's body. 53 | 54 | Hiccup is intelligent enough to render different HTML elements in different ways, in order to accommodate browser quirks: 55 | 56 | ```clojure 57 | (markup/html [:script]) 58 | ;; => "" 59 | 60 | (html [:p]) 61 | ;; => "

" 62 | ``` 63 | 64 | And provides a CSS-like shortcut for denoting `id` and `class` attributes: 65 | 66 | ```clojure 67 | (markup/html [:div#foo.bar.baz "bang"]) 68 | ;; => "

bang
" 69 | ``` 70 | 71 | When writing multiple lines of hiccup markup, wrap them in either a `[:div ]` or a `(list )` expression. 72 | 73 | ```clojure 74 | [:div 75 | [:h1 "My Picture Album"] 76 | [:img {:src seaside.png} "A sunny seaside view"] 77 | [:img {:src pier.png} "A walk along the pier"]] 78 | ``` 79 | 80 | 81 | If the body of the element is a seq, its contents will be expanded out into the element body. This makes working with forms like `map` and `for` more convenient: 82 | 83 | ```clojure 84 | (html [:ul 85 | (for [x (range 1 4)] 86 | [:li x])]) 87 | ;; => "
  • 1
  • 2
  • 3
" 88 | ``` 89 | 90 | 91 | ```clojure 92 | (html 93 | [:ul 94 | (for [x (range 1 4)] 95 | [:li x])]) 96 | ;; => "
  • 1
  • 2
  • 3
" 97 | ``` 98 | 99 | 100 | 101 | The parent tag will still be rendered in the above example, so 102 | 103 | > ####Hint:: Hiccup reference and guides 104 | > * [Hiccup API](https://weavejester.github.io/hiccup/) 105 | > * [Hiccup Tips - Lisp Cast](https://lispcast.com/hiccup-tips/) 106 | -------------------------------------------------------------------------------- /docs/projects/leiningen/todo-app/hiccup/updating-handlers-with-hiccup.md: -------------------------------------------------------------------------------- 1 | # Updating handlers with hiccup 2 | 3 | Instead of including fiddly html code (and having to make sure you close tags), we will write our markup in Clojure syntax using hiccup. 4 | 5 | ## Add dependencies 6 | 7 | > ####Note:: Add hiccup dependencies 8 | 9 | ```clojure 10 | [hiccup "1.0.5"] 11 | ``` 12 | 13 | ## Require hiccup 14 | 15 | > ####Note:: Add hiccup to your namespace 16 | 17 | ```clojure 18 | [hiccup.core :refer :all] 19 | [hiccup.page :refer :all] 20 | ``` 21 | 22 | Your core.clj file should look like this 23 | 24 | ```clojure 25 | (ns todo-list.core 26 | (:require [ring.adapter.jetty :as jetty] 27 | [ring.middleware.reload :refer [wrap-reload]] 28 | [compojure.core :refer [defroutes GET]] 29 | [compojure.route :refer [not-found]] 30 | [ring.handler.dump :refer [handle-dump]] 31 | [hiccup.core :refer :all] 32 | [hiccup.page :refer :all])) 33 | ``` 34 | 35 | ## Update the welcome handler 36 | 37 | > ####Note:: Change the `welcome` handler to use hiccup rather than html code 38 | 39 | ```clojure 40 | (defn welcome 41 | "A ring handler to respond with a simple welcome message" 42 | [request] 43 | (html [:h1 "Hello, Clojure World"] 44 | [:p "Welcome to your first Clojure app, I now update automatically"])) 45 | ``` 46 | 47 | The `html` function create html code based on the keywords used. However, the html function does not create a full html web page. 48 | 49 | 50 | ## Update the goodbye handler 51 | 52 | > ####Note:: Change the `goodbye` handler to use hiccup rather than html 53 | 54 | ```clojure 55 | (defn goodbye 56 | "A song to wish you goodbye" 57 | [request] 58 | (html5 {:lang "en"} 59 | [:head (include-js "myscript.js") (include-css "mystyle.css")] 60 | [:body 61 | [:div [:h1 {:class "info"} "Walking back to happiness"]] 62 | [:div [:p "Walking back to happiness with you"]] 63 | [:div [:p "Said, Farewell to loneliness I knew"]] 64 | [:div [:p "Laid aside foolish pride"]] 65 | [:div [:p "Learnt the truth from tears I cried"]]])) 66 | ``` 67 | 68 | Using the `html5` function a complete html page is created, with a header and body section. 69 | 70 | See the [hiccup.page API documentation](http://weavejester.github.io/hiccup/hiccup.page.html). 71 | -------------------------------------------------------------------------------- /docs/projects/leiningen/todo-app/index.md: -------------------------------------------------------------------------------- 1 | # TODO web application - Leiningen project 2 | 3 | > #### TODO::Content update through Winter 2020 4 | >This is the original project example developed several years ago, so some of the library versions may be out of date. 5 | > 6 | > Practicalli recommends using next.jdbc to talk to postgresql and examples of this are covered in the new content being created for the [banking on clojure project](/projects/banking-on-clojure/). 7 | 8 | 9 | A simple todo list server side web application 10 | 11 | The TODO work for the todo project includes: 12 | 13 | * Leiningen - project configuration and build (add Clojure CLI tools) 14 | * ring - jetty application server and request/response management (review) 15 | * compojure - routing of requests (review) 16 | * hiccup - HTML content written using Clojure (review) 17 | * Postgresql - relational database for todo items (add next.jdbc library) 18 | * CircleCI - continuous testing and integration 19 | * Heroku - deployment to staging and production (pipeline) 20 | -------------------------------------------------------------------------------- /docs/projects/leiningen/todo-app/introducing-ring/index.md: -------------------------------------------------------------------------------- 1 | # Introducing Ring 2 | 3 | Web applications typically run on a web or application server, such as [Tomcat](http://tomcat.apache.org/) or [Jetty](http://www.eclipse.org/jetty/) that provide a [Java Servlet Container](https://en.wikipedia.org/wiki/Java_servlet). 4 | 5 | The Ring library provides a way to use these servers without being tied to any specific implementation. Ring provides a common way to 6 | 7 | * Write your application using Clojure functions and maps 8 | * Run your application in an auto-reloading development server (wrap-reload) 9 | * Compile your application into a Java Servlet application 10 | * Package your application into a Java war file 11 | * Use a selection of [middleware functions](https://github.com/ring-clojure/ring/wiki/Standard-middleware) 12 | * Deploy your application in cloud environments like Heroku 13 | 14 | In essence, Ring converts the requests that come from the browser into a Clojure map, the request map. The request map may be passed through one or more middleware functions before being converted to a response map by a handler. The response map may be processed by one or more middleware functions before being converted by Ring to a web server response. 15 | 16 | ![Ring conceptual view](../images/clojure-ring-concept.png) 17 | 18 | In the following sections you will get a better understanding of 19 | 20 | * how to represent a handler 21 | * what do requests and responses look like 22 | * how to separate query parameters from the design of your web app 23 | 24 | Ring is the current de facto standard library used to write web applications in Clojure. Higher level frameworks such as Compojure use Ring as a common basis. 25 | -------------------------------------------------------------------------------- /docs/projects/leiningen/todo-app/postgres/connect-to-heroku-postgres-from-clients.md: -------------------------------------------------------------------------------- 1 | # Connecting to Heroku Postgres from Postgres Clients 2 | 3 | * Command Line 4 | * GUI tools 5 | * Operations tools 6 | 7 | Heroku Postgres databases are accessible from anywhere via a secure http connection, so you can connect your favourite Postgres client. 8 | 9 | ## View Provisioned Postgres Data stores 10 | 11 | Login to https://data.heroku.com/ to see all the provisioned Heroku Postgres data stores (postgres, redis, etc.) and data clips. 12 | 13 | ![Heroku Data Dashboard - data stores](https://raw.githubusercontent.com/practicalli/graphic-design/live/heroku/screenshots/heroku-data-dashboard-datastores.png) 14 | 15 | ## View Datastore add-on 16 | 17 | Login to https://heroku.com/ to see the dashboard of Heroku Applications created for that account. Select a specific Application to see what Datastore add-on is attached 18 | 19 | ![Heroku Dashboard - Banking on Clojure overview](https://raw.githubusercontent.com/practicalli/graphic-design/live/heroku/screenshots/heroku-dashboard-banking-on-clojure-staging-overview.png) 20 | 21 | 22 | ## DATABASE_URL Configuration Variable 23 | 24 | Provisioning an Heroku Postgres add-on automatically adds a `DATABASE_URL` environment variable to the Heroku app. Use this value to connect consistently throughout the life of your database from the Heroku application 25 | 26 | To see the value of the `DATABSAE_URL` use the Heroku Toolbelt command `heroku config`, specifying the app name if you created multiple Heroku apps for the current project 27 | 28 | ```bash 29 | heroku config 30 | 31 | heroku config --app my-app-name 32 | ``` 33 | 34 | ![Heroku Toolbelt - view the DATABASE_URL]() 35 | 36 | 37 | > #### Hint::Database Clients and other Services 38 | > The value of the DATABASE_URL can be used to connect remote database clients (DBeaver, PGAdmin) as well as other services that require a data store. 39 | -------------------------------------------------------------------------------- /docs/projects/leiningen/todo-app/postgres/dataclips.md: -------------------------------------------------------------------------------- 1 | # Heroku Dataclips 2 | 3 | Its very easy to create quick reports on your Heroku Postgres database using Dataclips and share the results with your company. 4 | 5 | Heroku Dataclips allow you to write SQL queries that run on Heroku Postgres. You can then share these queries along with the results via a web address. 6 | 7 | You can give people the ability to create their own query based on yours and share their version of the query and results. Its kind of Github or Gists for databases. 8 | 9 | ## Finding tables in Heroku Postgres 10 | 11 | 12 | ```sql 13 | SELECT * FROM pg_catalog.pg_tables WHERE schema-name != 'pg_catalog' AND schema-name != 'information_schema' 14 | ``` 15 | -------------------------------------------------------------------------------- /docs/projects/leiningen/todo-app/postgres/environment-variables.md: -------------------------------------------------------------------------------- 1 | # Environment Variables 2 | 3 | Add an Heroku Postgres database to the Heroku app creates a `DATABASE_URL` configuration variable, an environment variable manged by Heroku. This configuration variable can be used to avoid including database connection details in the code repository. 4 | 5 | > ####Note:: Check your Heroku app has a `DATABASE_URL` configuration variable 6 | > List all the configuration variables for your app using the command: 7 | > 8 | ```bash 9 | heroku config 10 | ``` 11 | > 12 | > The `DATABASE_URL` contains your **username** and **password** for the database, as well as the **hostname** and **database name** in the form of: 13 | > 14 | ```clojure 15 | "postgres://username:password@hostname/database-name" 16 | ``` 17 | 18 | 19 | ## Using PostgreSQL client applications 20 | The Heroku Postgres database is available via an secure connection from anywhere on the Internet, so you can use these details with your favourite postgres client. The postgres client must connect over SSL, or the connection will be rejected by Heroku postgres. 21 | -------------------------------------------------------------------------------- /docs/projects/leiningen/todo-app/postgres/index.md: -------------------------------------------------------------------------------- 1 | # Postgres Database 2 | 3 | > #### TODO::Content may be a little dated, sorry 4 | > Content update during the Winter of 2020 5 | 6 | Postgres is a modern and powerful relational database that also supports storing of json, xml and object relationships. Postgres has a strong open source community behind it and is actively maintained. Postgres is also highly scalable database with drivers for all the major programming languages. 7 | 8 | In this workshop we are going to use Heroku Postgres, a database on demand service that requires no local installation. 9 | 10 | > ####Hint:: Alternatively, you can use a local instance of Postgres if you are happy to run it on your laptop. 11 | 12 | ## next.jdbc - a modern approach to relational databases with Clojure 13 | See the [next.jdbc getting started guide](https://cljdoc.org/d/seancorfield/next.jdbc/1.1.547/doc/getting-started) for lots of useful information on writing SQL queries in Clojure. 14 | 15 | 16 | 17 | ## PostgreSQL Resources 18 | * [Heroku Postgres](https://www.heroku.com/postgres) 19 | * [Amazon Relational Database Service (RDS)](https://aws.amazon.com/rds/) - Amazon Aurora, PostgreSQL, MySQL, MariaDB, Oracle Database, and SQL Server 20 | * [IBM Cloud: Postgres services](https://www.ibm.com/uk-en/cloud/databases-for-postgresql) 21 | * [What is PostgreSQL](https://www.postgresqltutorial.com/what-is-postgresql/) - postgresqltutorial.com 22 | -------------------------------------------------------------------------------- /docs/projects/leiningen/todo-app/postgres/install.md: -------------------------------------------------------------------------------- 1 | # Postgres install 2 | 3 | Using infrastructure or software as a service databases are provisioned, usually by issuing a simple command or using a web based dashboard for that service. 4 | 5 | Using the Heroku app created previously, a Posgres database will be provisioned. 6 | 7 | In the root of your Clojure project, run the following Heroku toolbelt command to add a Postgres database to your existing Heroku app 8 | 9 | ```bash 10 | heroku addons:create heroku-postgresql 11 | ``` 12 | 13 | 14 | ## Local PostgreSQL Install 15 | * [Install Postgresql locally - postgresql.org](https://www.postgresql.org/docs/12/tutorial-install.html) 16 | * [Ubuntu documentation: PostgreSQL](https://help.ubuntu.com/community/PostgreSQL) 17 | * [Install and use postgresql on Ubuntu 20.04](https://www.digitalocean.com/community/tutorials/how-to-install-and-use-postgresql-on-ubuntu-20-04) - digitalocean 18 | * [Ubuntu Linux PostgreSQL downloads](https://www.postgresql.org/download/linux/ubuntu/) - postgresql.org 19 | -------------------------------------------------------------------------------- /docs/projects/leiningen/todo-app/postgres/pg-admin.md: -------------------------------------------------------------------------------- 1 | # pgAdmin 2 | -------------------------------------------------------------------------------- /docs/projects/leiningen/todo-app/refactor-namespace/base-routes.md: -------------------------------------------------------------------------------- 1 | # Base routes 2 | 3 | 4 | > ####Note:: Create a new file called `src/todo_list/handlers/base-routes.clj` and move all the handler code into this file from `src/todo_list/core`. Make sure you also move the `hiccup` libraries into the new `handlers` namespace. 5 | 6 | 7 | 8 | `handlers.clj` should look as follows: 9 | 10 | 11 | ```clojure 12 | (ns todo-list.handlers 13 | (:use 14 | [hiccup.core] 15 | [hiccup.page])) 16 | 17 | 18 | (defn welcome 19 | "A ring handler to respond with a simple welcome message" 20 | [request] 21 | (html [:h1 "Hello, Clojure World"] 22 | [:p "Welcome to your first Clojure app, I now update automatically"])) 23 | 24 | (defn goodbye 25 | "A song to wish you goodbye" 26 | [request] 27 | (html5 {:lang "en"} 28 | [:head (include-js "myscript.js") (include-css "mystyle.css")] 29 | [:body 30 | [:div [:h1 {:class "info"} "Walking back to happiness"]] 31 | [:div [:p "Walking back to happiness with you"]] 32 | [:div [:p "Said, Farewell to loneliness I knew"]] 33 | [:div [:p "Laid aside foolish pride"]] 34 | [:div [:p "Learnt the truth from tears I cried"]]])) 35 | 36 | (defn about 37 | "Information about the website developer" 38 | [request] 39 | (html [:h1 "About the Website"] 40 | [:p "I am an awesome Clojure developer, well getting there... trying some Hiccup now"])) 41 | 42 | (defn hello 43 | "A simple personalised greeting showing the use of variable path elements" 44 | [request] 45 | (let [name (get-in request [:route-params :name])] 46 | {:status 200 47 | :body (str "Hello " name ". I got your name from the web URL") 48 | :headers {}})) 49 | 50 | (def operands {"+" + "-" - "*" * ":" /}) 51 | 52 | (defn calculator 53 | "A very simple calculator that can add, divide, subtract and multiply. This is done through the magic of variable path elements." 54 | [request] 55 | (let [a (Integer. (get-in request [:route-params :a])) 56 | b (Integer. (get-in request [:route-params :b])) 57 | op (get-in request [:route-params :op]) 58 | f (get operands op)] 59 | (if f 60 | {:status 200 61 | :body (str "

Result = " (f a b) "

") 62 | :headers {}} 63 | {:status 404 64 | :body "Sorry, unknown operator. I only recognise + - * : (: is for division)" 65 | :headers {}}))) 66 | 67 | (defn trying-hiccup 68 | [request] 69 | (html5 {:lang "en"} 70 | [:head (include-js "myscript.js") (include-css "mystyle.css")] 71 | [:body 72 | [:div [:h1 {:class "info"} "This is Hiccup"]] 73 | [:div [:p "Take a look at the HTML generated in this page, compared to the about page"]] 74 | [:div [:p "Style-wise there is no difference between the pages as we haven't added anything in the stylesheet, however the hiccup page generates a more complete page in terms of HTML"]]])) 75 | ``` 76 | -------------------------------------------------------------------------------- /docs/projects/leiningen/todo-app/refactor-namespace/code-so-far.md: -------------------------------------------------------------------------------- 1 | # Code so far 2 | 3 | -------------------------------------------------------------------------------- /docs/projects/leiningen/todo-app/refactor-namespace/core.md: -------------------------------------------------------------------------------- 1 | # Refactored Core 2 | 3 | Refactor the core namespace to contain the code that starts up our server and join up all the route handlers. 4 | 5 | Edit the `src/todo_list/core.clj` file and update the namespace definition to include the new `handlers` namespace, including the whole namespace in `core`. 6 | 7 | ```clojure 8 | (ns todo-list.core 9 | (:require [compojure.core :refer [routes]] 10 | [todo-list.handlers.play :refer [play-routes]] 11 | [todo-list.handlers.task :refer [task-routes]] 12 | [todo-list.handlers.base :refer [base-routes]] 13 | [ring.middleware.reload :refer [wrap-reload]] 14 | [ring.adapter.jetty :as jetty])) 15 | ``` 16 | 17 | Change app from a defroutes to a `def` and use the `route` function to merge all the defroutes into one 18 | 19 | ```clojure 20 | (def app 21 | (routes #'play-routes #'base-routes #'task-routes)) 22 | ``` 23 | 24 | The `routes` function takes the names of all the other defroutes and merges into one list of handlers. 25 | 26 | `app` is now just a name we give to reference the handlers for all routes. We can continue to add more defroutes to app as our application grows, along with any middleware we wish to apply to our handlers. 27 | 28 | `core` should be much smaller, containing only the route definition and the main app (plus the middleware around the app) 29 | 30 | ```clojure 31 | (ns todo-list.core 32 | (:require [compojure.core :refer [routes]] 33 | [todo-list.handlers.play :refer [play-routes]] 34 | [todo-list.handlers.task :refer [task-routes]] 35 | [todo-list.handlers.base :refer [base-routes]] 36 | [ring.middleware.reload :refer [wrap-reload]] 37 | [ring.adapter.jetty :as jetty])) 38 | 39 | (def app 40 | (routes #'play-routes #'base-routes #'task-routes)) 41 | 42 | (defn -main 43 | "A very simple web server using Ring & Jetty" 44 | [port-number] 45 | (jetty/run-jetty app 46 | {:port (Integer. port-number)})) 47 | 48 | (defn -dev-main 49 | "A very simple web server using Ring & Jetty that reloads code changes via the development profile of Leiningen" 50 | [port-number] 51 | (jetty/run-jetty (wrap-reload #'app) 52 | {:port (Integer. port-number)})) 53 | ``` 54 | -------------------------------------------------------------------------------- /docs/projects/leiningen/todo-app/refactor-namespace/index.md: -------------------------------------------------------------------------------- 1 | # Refactor Core Namespace 2 | 3 | The `todo-list.core` namespace is getting quite full and only going to get more code, unless we refactor the design and create some additional namespaces. 4 | 5 | A namespace is a way to group behaviour (functions) and data (data structures / defs) in one scope. Functions can be defined as private to that scope, so only other functions in the same namespace can call them. 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /docs/projects/leiningen/todo-app/refactor-namespace/play-routes.md: -------------------------------------------------------------------------------- 1 | # Play routes 2 | -------------------------------------------------------------------------------- /docs/projects/leiningen/todo-app/refactor-namespace/task-routes.md: -------------------------------------------------------------------------------- 1 | # Task routes 2 | -------------------------------------------------------------------------------- /docs/projects/leiningen/todo-app/reloading-the-application/code-so-far.md: -------------------------------------------------------------------------------- 1 | # The code so far 2 | 3 | The code and configuration we have created so far are in the [clojure-todo-list-example repository](https://github.com/practicalli/clojure-todo-list-example) github repository. 4 | 5 | Code for this section is in the branch called `03-reloading-the-application` 6 | 7 | If something is not working or you want to speed up, simply clone the project into a new directory using the command: 8 | 9 | ```bash 10 | git clone https://github.com/practicalli/clojure-todo-list-example 11 | ``` 12 | Once you have cloned the project, checkout the `03-reloading-the-application` branch 13 | 14 | ```bash 15 | git checkout 03-reloading-the-application 16 | ``` 17 | -------------------------------------------------------------------------------- /docs/projects/leiningen/todo-app/reloading-the-application/middleware.md: -------------------------------------------------------------------------------- 1 | # Middleware in Ring 2 | 3 | Middleware in ring is a way to modify the incoming requests or outgoing responses. 4 | 5 | Middleware can also wrap handlers or other middleware, affecting their behaviour. For example the `wrap-reload` middleware enables live reloading by detecting file changes and reloading affected functions into their namespace, before the request is passed to the relevant handler function 6 | 7 | Middleware in `ring/ring-core` 8 | 9 | * [wrap-cookies](https://github.com/mmcgrana/ring/blob/master/ring-core/src/ring/middleware/cookies.clj#L124) (ring.middleware.cookies) 10 | * [wrap-file](https://github.com/mmcgrana/ring/blob/master/ring-core/src/ring/middleware/file.clj#L14) (ring.middleware.file) 11 | * [wrap-file-info](https://github.com/mmcgrana/ring/blob/master/ring-core/src/ring/middleware/file_info.clj#L89) (ring.middleware.file-info) 12 | * [wrap-flash](https://github.com/mmcgrana/ring/blob/master/ring-core/src/ring/middleware/flash.clj#L4) (ring.middleware.flash) 13 | * [wrap-keyword-params](https://github.com/mmcgrana/ring/blob/master/ring-core/src/ring/middleware/keyword_params.clj#L15) (ring.middleware.keyword-params) 14 | * [wrap-multipart-params](https://github.com/mmcgrana/ring/blob/master/ring-core/src/ring/middleware/multipart_params.clj#L60) (ring.middleware.multipart-params 15 | * [wrap-nested-params](https://github.com/mmcgrana/ring/blob/master/ring-core/src/ring/middleware/nested_params.clj#L47) (ring.middleware.nested-params 16 | * [wrap-params](https://github.com/mmcgrana/ring/blob/master/ring-core/src/ring/middleware/params.clj#L54) (ring.middleware.params) 17 | * [wrap-session](https://github.com/mmcgrana/ring/blob/master/ring-core/src/ring/middleware/session.clj#L6) (ring.middleware.session) 18 | 19 | Middleware in `ring/ring-devel` 20 | 21 | * [wrap-lint](https://github.com/mmcgrana/ring/blob/master/ring-devel/src/ring/middleware/lint.clj#L84) (ring.middleware.lint) 22 | * [wrap-reload](https://github.com/mmcgrana/ring/blob/master/ring-devel/src/ring/middleware/reload.clj#L4) (ring.middleware.reload) 23 | * [wrap-stacktrace](https://github.com/mmcgrana/ring/blob/master/ring-devel/src/ring/middleware/stacktrace.clj#L75) (ring.middleware.stacktrace) 24 | -------------------------------------------------------------------------------- /docs/projects/leiningen/todo-app/reloading-the-application/test-your-code-reloads.md: -------------------------------------------------------------------------------- 1 | # Test the wrap-reload middleware 2 | 3 | Make a change to the `welcome` function code and check that it automatically reloads. 4 | 5 | Change the default response text in the `welcome` function 6 | 7 | Open the webapp in the browser http://localhost:8000. 8 | 9 | Make a change to the code in the `welcome` function, altering the text of the `:body` in the default request. 10 | 11 | ```clojure 12 | (defn welcome 13 | "A ring handler to process all requests for the web server. 14 | If a request is for something other than `/` then an error message is returned" 15 | [request] 16 | (if (= "/" (:uri request)) 17 | {:status 200 18 | :headers {} 19 | :body "

Hello, Clojure World

20 |

Welcome to your first Clojure app.

21 |

I now reload changes automatically

"} 22 | {:status 404 23 | :headers {} 24 | :body "

This is not the page you are looking for

25 |

Sorry, the page you requested was not found!

"})) 26 | ``` 27 | 28 | Save the code change and refresh your browser page, you should now see the updated message. 29 | -------------------------------------------------------------------------------- /docs/projects/leiningen/todo-app/task-handlers/add-a-task.md: -------------------------------------------------------------------------------- 1 | # Add a task 2 | 3 | > #### TODO::work in progress, sorry 4 | 5 | which namespace are these going in 6 | 7 | (ns todo-list.handlers.tasks 8 | :requires models....) 9 | -------------------------------------------------------------------------------- /docs/projects/leiningen/todo-app/task-handlers/delete-a-task.md: -------------------------------------------------------------------------------- 1 | # Delete a task 2 | 3 | > #### TODO::work in progress, sorry 4 | -------------------------------------------------------------------------------- /docs/projects/leiningen/todo-app/task-handlers/index.md: -------------------------------------------------------------------------------- 1 | # Task handlers 2 | 3 | > #### TODO::work in progress, sorry 4 | 5 | Now we have functions that create the database and add / remove tasks, we can provide handlers to call these functions and therefore enable the user to add and remove tasks. 6 | 7 | In the following pages we will create handlers and use hiccup for the html markup. 8 | -------------------------------------------------------------------------------- /docs/projects/leiningen/todo-app/task-handlers/show-task.md: -------------------------------------------------------------------------------- 1 | # Show tasks 2 | 3 | > #### TODO::work in progress, sorry 4 | -------------------------------------------------------------------------------- /docs/projects/leiningen/todo-app/unit-test-handler-function/index.md: -------------------------------------------------------------------------------- 1 | # Unit Test handler functions 2 | 3 | Handler functions can be tested with unit tests as they are just pure functions. All handlers take a request hash-map and return a response hash-map. So its easy to give each handler a hash-map as an argument and test that we get the expected response hash-map in return. 4 | 5 | There is no need to mock the framework until we do integration level testing, where we are testing the full lifecycle of request-response. 6 | 7 | It is useful to have separate unit and integration tests to quickly narrow down the root cause of issues. 8 | 9 | ## Unit test branch 10 | 11 | The unit tests are placed under `test/full_namespace_path/` and reside in files with the same names as the source code filenames, with `-test` postfixed to the end. 12 | 13 | ```bash 14 | src/practicalli/simple_webapp/handlers.clj 15 | test/practicalli/simple_webapp/handlers-test.clj 16 | ``` 17 | 18 | ## Writing unit tests 19 | 20 | `clojure.test` is used to write unit tests for handlers, as we are just treating them as functions. 21 | -------------------------------------------------------------------------------- /docs/projects/leiningen/working-example/index.md: -------------------------------------------------------------------------------- 1 | # A working example 2 | 3 | As we don't have time to build a full web app in this workshop, here is one that was built early. 4 | 5 | ![Shouter](../images/shouter-web-ui-example.png) 6 | 7 | > ####Note:: Checkout the [Shouter code from Gitub](https://github.com/jr0cket/shouter) 8 | -------------------------------------------------------------------------------- /docs/projects/slack-app/index.md: -------------------------------------------------------------------------------- 1 | # Clojure powered Slack Application 2 | 3 | Slack applications provide a way to extend the functionality of Slack and provide integration with other services, e.g. GitHub, Atlassian Jira & Confluence, etc. 4 | 5 | 6 | ## Development process Overview 7 | 8 | - create a slack account and slack space (to test the application) 9 | - create a slack app 10 | - deploy the slack app in the slack test workspace created previously 11 | - Create a clojure project and interact with the Slack API 12 | - Setup ngrok domain for Slack app to access a locally running Clojure web service (to respond to interaction with the app - assuming the slack isnt just push driven) 13 | - alternatively, webservices connection can be configured between the locally running Clojure app and slack (desktop app required? - how does this work...) 14 | 15 | - deploy clojure application to public cloud (AWS, Render.com, etc) 16 | 17 | -------------------------------------------------------------------------------- /docs/projects/slack-app/slack-api-methods.md: -------------------------------------------------------------------------------- 1 | # Slack API Methods 2 | 3 | An access token allows the calling of the methods described by the scopes requested during a Slack App installation, 4 | e.g., the chat:write scope allows an app to post messages. 5 | 6 | The app isn't a member of any channels when installed. Choose a channel suitable for testing purposes and `/invite` the Slack app to the channel. 7 | 8 | You can find the corresponding id for the channel that your app just joined by looking through the results of the conversations.list method: 9 | 10 | ```shell 11 | curl https://slack.com/api/conversations.list -H "Authorization: Bearer xoxb-1234..." 12 | ``` 13 | 14 | You'll receive a list of conversation objects. 15 | 16 | Now, post a message to the same channel your app just joined with the chat.postMessage method: 17 | 18 | ```shell 19 | curl -X POST -F channel=C1234 -F text="Reminder: we've got a softball game tonight!" https://slack.com/api/chat.postMessage -H "Authorization: Bearer xoxb-1234..." 20 | ``` 21 | 22 | ![Slack Web UI - Web API Methods search](https://github.com/practicalli/graphic-design/blob/live/clojure-web-services/slack-app/slack-app-api-settings-add-app-to-workspace-allow.png?raw=true#only-dark#){loading=lazy} 23 | 24 | 25 | Voila! We're already well on our way to putting a full-fledged Slack app on the table. 26 | 27 | [Web API Guide](https://api.slack.com/web) 28 | 29 | [API Methods list](https://api.slack.com/methods) 30 | 31 | [Interactive Workflows](https://api.slack.com/interactivity) 32 | 33 | -------------------------------------------------------------------------------- /docs/projects/slack-app/slack-scopes.md: -------------------------------------------------------------------------------- 1 | # Slack Scopes Overview 2 | 3 | Scopes give the app permission to carry out actions, e.g. post messages, in the development workspace. 4 | 5 | Open the development workspace, either in a web page or in the Slack desktop app. 6 | 7 | **Sidebar** > **OAuth & Permissions** > **Scopes** > **Add an OAuth Scope** 8 | 9 | - `chat:write` scope to the Bot Token to allow the app to post messages 10 | - `channels:read` scope too so your app can gain knowledge about public Slack channels 11 | - `commands` scope to build a Slash command. 12 | - `incoming-webhook` scope to use Incoming Webhooks. 13 | - `chat:write.public` scope to gain the ability to post in all public channels, without joining. Otherwise, you'll need to use conversations.join, or have your app invited by a user into a channel, before you can post. 14 | - `chat:write.customize` scope to adjust the app's message authorship to make use of the username, icon_url, and icon_emoji parameters in `chat.postMessage`. 15 | 16 | 17 | > Add scopes to the Bot Token. 18 | > 19 | > Only add scopes to the User Token when the app needs to act as a specific user (e.g. post message as user, set user status, etc.) 20 | 21 | 22 | If the scopes applied to a Slack App are changed, the Slack App must be redeployed to the workspace (and Slack App Directory) for the changes to take effect. 23 | 24 | ![Slack Web UI - Warning changed slack app permission scopes - reinstall the app](https://github.com/practicalli/graphic-design/blob/live/clojure-web-services/slack-app/slack-app-settings-oauth-scopes-reinstall-warning.png?raw=true#only-dark#){loading=lazy} 25 | 26 | 27 | -------------------------------------------------------------------------------- /docs/projects/status-monitor-deps/continuous-integration.md: -------------------------------------------------------------------------------- 1 | # Continuous Integration 2 | 3 | To assist in the development of the application, CircleCI, a continuous integration service will be used. 4 | 5 | Initially this will run all the unit tests for the application and report on the results. In another chapter, CircleCI will be used to package the application and deploy the application. 6 | 7 | 8 | ## Alias for test runner 9 | 10 | Edit `deps.edn` file and add an alias called `:test/run` that calls kaocha test runner on the code 11 | 12 | ```clojure 13 | :test/run 14 | {:extra-paths ["test"] 15 | :extra-deps {lambdaisland/kaocha {:mvn/version "1.71.1119"}} 16 | :main-opts ["-m" "kaocha.runner"] 17 | :exec-fn kaocha.runner/exec-fn 18 | :exec-args {:randomize? false 19 | :fail-fast? true}} 20 | ``` 21 | 22 | Check the test runner is working by running the `clojure` command with the `:test/run` alias in a terminal at the root of the Clojure project 23 | 24 | ```bash 25 | clojure -X:test/run 26 | ``` 27 | 28 | 29 | ## Add CircleCI configuration 30 | 31 | Edit `.circleci/config.yml` and add a configuration to build and test the Clojure application. The `cimg/clojure:1.10.0` image contains OpenJDK 17 and the latest version of Clojure CLI, Leiningen and Babashka. 32 | 33 | > Run the `clojure` commands in the root of the project before adding the configuration, to ensure the commands work locally first. 34 | 35 | 36 | 37 | ```yaml 38 | version: 2.0 39 | jobs: 40 | build: 41 | working_directory: ~/build 42 | docker: 43 | - image: cimg/clojure:1.10 44 | environment: 45 | JVM_OPTS: -Xmx3200m 46 | steps: 47 | - checkout 48 | - restore_cache: 49 | key: status-monitor-service-{{ checksum "deps.edn" }} 50 | - cache-dependencies 51 | - run: clojure -P 52 | - save_cache: 53 | paths: 54 | - ~/.m2 55 | - ~/.gitlibs 56 | key: status-monitor-service-{{ checksum "deps.edn" }} 57 | - Unit-testing 58 | - run: clojure -X:test/run 59 | ``` 60 | 61 | 62 | ## Add project on CircleCI 63 | 64 | Visit the CircleCI dashboard and select **Add Projects**. Find the `status-monitor-service` repository and select **Set Up Project** button. 65 | 66 | Choose the **Add Manual** install and **Start Building** 67 | 68 | ![CircleCI dashboard - status-monitor-service pipelines successful build](https://raw.githubusercontent.com/practicalli/graphic-design/live/clojure-web-services/circle-ci-status-monitor-pipelines-success-light.png) 69 | -------------------------------------------------------------------------------- /docs/projects/status-monitor-deps/index.md: -------------------------------------------------------------------------------- 1 | # Status Monitor project with Clojure tools 2 | A status monitor dashboard to show operational status of a range of services. 3 | 4 | A server-side web application using 5 | - ring and compojure for webapp request management 6 | - bulma CSS library for styling 7 | - hiccup for writing html in Clojure syntax 8 | - SVG graphics for status graphics 9 | - clojure.spec for validating functions and svg definitions in Clojure 10 | 11 | 12 | ## Creating a project 13 | 14 | === "deps-new" 15 | Create a project using the app template and called practicalli/status-monitor. The `:project/create` alias from [Practicalli Clojure CLI Config](https://practical.li/clojure/clojure-cli/practicalli-config/) uses the deps-new project to create Clojure projects 16 | ```bash 17 | clojure -T:project/create :template app :name practicalli/status-monitor 18 | ``` 19 | Some minor tweaks are made to the project before starting the application development 20 | 21 | - describe the project and how it can be used in the README 22 | - delete the LICENSE file and use a Creative Commons in the README 23 | - format the deps.edn file for readability 24 | 25 | 26 | === "Manual" 27 | Create a project in a directory called status-monitor, with a `deps.end` file in the root of that directory 28 | ```clojure title="deps.edn" 29 | {:paths ["src"] 30 | :deps {org.clojure/clojure {:mvn/version "1.11.3"}}} 31 | ``` 32 | Create a `src/practicalli/status_monitor.clj` file 33 | ```clojure title="src/practicalli/status_monitor.clj" 34 | (ns practicalli.status-monitor) 35 | ``` 36 | Create a `test/practicalli/status_monitor_test.clj` file 37 | ```clojure title="src/practicalli/status_monitor.clj" 38 | (ns practicalli.status-monitor-test 39 | (:require [clojure.test :refer [deftest is testing]])) 40 | ``` 41 | 42 | 43 | !!! EXAMPLE "practicalli/status-monitor" 44 | The code for this project can be found at [practicalli/status-monitor](https://github.com/practicalli/status-monitor) 45 | -------------------------------------------------------------------------------- /docs/projects/status-monitor-deps/unit-test-mocking-handlers.md: -------------------------------------------------------------------------------- 1 | # Mocking in Unit Tests 2 | 3 | The main focus of unit tests in a web application are the handler functions, passing requests to those functions and checking the responses. 4 | 5 | All handler functions are passed a request object by default when using compojure `defroutes` function for routing. 6 | 7 | If handler functions do not use arguments then you can test those handlers by simply passing an empty hash-map, `{}`. 8 | 9 | For all other handler functions you can pass a request object or just specific parts of a request in a hash-map. 10 | 11 | 12 | ## Ring mock library 13 | 14 | [ring-mock](https://github.com/ring-clojure/ring-mock) is a small library creating Ring request maps (Clojure hash-maps) to support unit testing. Generated hash-maps are examples of a ring request and used as arguments when calling the handler functions in tests. 15 | 16 | 17 | ## Add dev dependency 18 | 19 | As ring-mock is a development only library, it should be added to an alias not included in the packaging of the project for production. 20 | 21 | Edit the project `deps.edn` file in the project and add ring-mock to an alias called `:env/dev`, creating the alias if required. 22 | 23 | 24 | ```clojure 25 | :env/dev 26 | {:extra-deps {ring/ring-mock {:mvn/version "0.4.0"}}} 27 | ``` 28 | 29 | ## Add namespace 30 | 31 | Add the ring-mock.request namespace to any of the test namespaces mocking of requests will be useful. 32 | 33 | Edit the `test/practicalli/status-monitor-service.clj` and add ring-mock as a required namespace in files `ns` form. 34 | 35 | ```clojure 36 | (ns practicalli.status-monitor-service-test 37 | (:require [clojure.test :refer [deftest is testing]] 38 | [ring.mock.request :as mock] 39 | [practicalli.status-monitor-service :as status-monitor])) 40 | ``` 41 | 42 | Add unit tests to check the handlers (which are going to be added next - TDD style) 43 | 44 | ```clojure 45 | (deftest test-app 46 | (testing "main route" 47 | (let [response ((status-monitor/app) (request :get "/"))] 48 | (is (= 200 (:status response))))) 49 | 50 | (testing "not-found route" 51 | (let [response ((status-monitor/app) (request :get "/invalid"))] 52 | (is (= 404 (:status response)))))) 53 | ``` 54 | 55 | 56 | ## Examples 57 | 58 | * [API: ring-mock](https://ring-clojure.github.io/ring-mock/ring.mock.request.html) 59 | 60 | ```clojure 61 | (deftest your-handler-test 62 | (is (= (your-handler (mock/request :get "/doc/10")) 63 | {:status 200 64 | :headers {"content-type" "text/plain"} 65 | :body "Your expected result"}))) 66 | 67 | (deftest your-json-handler-test 68 | (is (= (your-handler (-> (mock/request :post "/api/endpoint") 69 | (mock/json-body {:foo "bar"}))) 70 | {:status 201 71 | :headers {"content-type" "application/json"} 72 | :body {:key "your expected result"}}))) 73 | ``` 74 | -------------------------------------------------------------------------------- /docs/projects/todo-tracker/index.md: -------------------------------------------------------------------------------- 1 | # TODO Tracker 2 | 3 | A relatilvely simple web application to create and manage a todo list of tasks. 4 | 5 | 6 | 7 | ## Stack 8 | 9 | - programming language: Clojure 10 | - http server: http-kit 11 | - request routing: reitit (reitit-ring) 12 | - web page content: hiccup & bulma.io CSS 13 | - logging: mulog 14 | 15 | Continuous Integration managed via GitHub workflows 16 | 17 | !!! INFO "Example project on GitHub" 18 | TODO 19 | 20 | ## Create project 21 | 22 | Create a project using the Practicalli Project Templates 23 | 24 | !!! NOTE "" 25 | ```shell 26 | clojure -T:project/create-local :template practicalli/service :name practicalli/todo-tracker 27 | ``` 28 | 29 | ## Project design overview 30 | 31 | web server 32 | 33 | 34 | routing and handlers 35 | 36 | 37 | 38 | Logging 39 | 40 | 41 | 42 | ## REPL workflow 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /docs/reference/continuous-integration/heroku/index.md: -------------------------------------------------------------------------------- 1 | # CI: Heroku 2 | 3 | Heroku is a now only a commercial service without a developer environment. 4 | 5 | Practicalli is looking into other services as that are more developer friendly. 6 | 7 | 8 | ## Heroku pipelines 9 | 10 | Using Heroku Pipelines the staging environment is promoted to production rather than being rebuilt 11 | 12 | ![Heroku Pipeline concept - staging and production](https://raw.githubusercontent.com/jr0cket/developer-guides/master/heroku-pipelines-staging-production.png) 13 | 14 | The Heroku dashboard can be used to promote the application into production, once the staging application is signed off. 15 | 16 | ![Heroku Pipeline banking-on-clojure](https://raw.githubusercontent.com/practicalli/graphic-design/live/continuous-integration/heroku/heroku-pipeline-banking-on-clojure.png) 17 | 18 | 19 | ## Heroku Build process 20 | 21 | The build process starts when commits are pushed to Heroku, either directly or via a continuous integration service (eg. CircleCI). 22 | 23 | [![Heroku build process](https://raw.githubusercontent.com/jr0cket/developer-guides/gh-pages/heroku-deployment-process-simplified.png)](https://raw.githubusercontent.com/jr0cket/developer-guides/gh-pages/heroku-deployment-process-simplified.png) 24 | -------------------------------------------------------------------------------- /docs/reference/index.md: -------------------------------------------------------------------------------- 1 | # reference 2 | -------------------------------------------------------------------------------- /docs/relational-databases-and-sql/h2-database.md: -------------------------------------------------------------------------------- 1 | # H2 Lightweight Relational Database 2 | -------------------------------------------------------------------------------- /docs/relational-databases-and-sql/h2-database/database-tools.md: -------------------------------------------------------------------------------- 1 | # Database tools 2 | [DBeaver](https://dbeaver.io/) is a free database tool that supports the H2 database and many other databases. 3 | 4 | ## Create a new connection 5 | Create a **New Connection** and select **Embedded > H2 database** 6 | 7 | Select a `*.mv.db file` as the path 8 | 9 | ![DBeaver database tool - Embedded H2 database connection](/images/dbeaver-h2-new-connection-banking-on-clojure.png) 10 | 11 | If the H2 driver is not installed in DBeaver, a will prompt will display to download it. 12 | 13 | ![DBeaver database tool - H2 database driver download](/images/dbeaver-h2-driver-download.png) 14 | 15 | Expand the connection to see the schema details 16 | 17 | ![DBeaver database tool - Embedded H2 database connection](/images/dbeaver-h2-connection-schema-details.png) 18 | 19 | ## H2 database single connection 20 | When connecting to the H2 database using a database management tool such ad DBeaver, the database is locked and prevents code from running in the REPL. 21 | 22 | ![Clojure WebApps - H2 Database error - file lock](/images/clojure-webapps-database-h2-error-locked-database-file.png) 23 | 24 | Close the connection in the database management tool to continue using the REPL. 25 | -------------------------------------------------------------------------------- /docs/relational-databases-and-sql/h2-database/schema-design.md: -------------------------------------------------------------------------------- 1 | # H2 Schema design 2 | Key concepts and syntax for designing database schema for the H2 database 3 | 4 | 5 | ## Auto-increment values in H2 database 6 | The `IDENTITY` type is used for automatically generating an incrementing 64-bit long integer in H2 database. 7 | 8 | ```sql 9 | CREATE TABLE public.account ( 10 | id IDENTITY NOT NULL PRIMARY KEY , 11 | name VARCHAR NOT NULL, 12 | number VARCHAR NOT NULL, 13 | sortcode VARCHAR NOT NULL, 14 | created TIMESTAMP WITH TIME ZONE NOT NULL); 15 | ``` 16 | 17 | The value for `id` is automatically generated by H2, so no need to provide a value for id in the SQL statement 18 | 19 | ```sql 20 | INSERT INTO public.account ( id, name, number, sortcode, created) 21 | VALUES ( ? , ? , ? , ? ); 22 | ``` 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | ## Resources 38 | * [next.jdbc](https://cljdoc.org/d/seancorfield/next.jdbc/) documentation and [next.jdbc db-types list](https://github.com/seancorfield/next-jdbc/blob/develop/src/next/jdbc/connection.clj#L52-L123) 39 | * [H2 Database website](http://www.h2database.com/) 40 | * [SQL Constraints - W3Schools.com](https://www.w3schools.com/sql/sql_constraints.asp) 41 | * [Purpose of constraint naming - Stack Overflow](https://stackoverflow.com/questions/1397440/what-is-the-purpose-of-constraint-naming) 42 | * [seancorfield/honeysql](https://github.com/seancorfield/honeysql) - SQL as data structures 43 | * [stack overflow - auto increment id in h2 database](https://stackoverflow.com/questions/9353167/auto-increment-id-in-h2-database) 44 | -------------------------------------------------------------------------------- /docs/relational-databases-and-sql/next-jdbc-library/database-specifications.md: -------------------------------------------------------------------------------- 1 | # Database Specifications 2 | 3 | Call the `next.jdbc/get-datasource` function with a database specification or a JDBC URL string 4 | 5 | A database specification is a hash map describing the database you wish to connect to. TODO: examples of database specifications 6 | 7 | A "database spec" is a Clojure map that specifies how to access the data source. Specify the database type, the database name, and the username and password. 8 | 9 | ```clojure 10 | (def db-spec 11 | {:dbtype "mysql" 12 | :dbname "db-name" 13 | :user "user-account" 14 | :password "secret"}) 15 | ``` 16 | 17 | > use aero to use a different database specification based on the environment being run (dev, test, prod, etc.) 18 | > 19 | > next.jdbc also works with connection pooling libraries which can be used to construct a datasource from. Examples include HikariCP or c3p0 20 | -------------------------------------------------------------------------------- /docs/relational-databases-and-sql/postgresql-database.md: -------------------------------------------------------------------------------- 1 | # Postgresql database 2 | [PostgreSQL](https://www.postgresql.org/) is a powerful, open source object-relational database system with over 30 years of active development that has earned it a strong reputation for reliability, feature robustness, and performance. 3 | 4 | PostgreSQL is a common choice for Clojure WebApps that require a persistent store for data, especially where that data is relational in nature. PostgreSQL is also a great choice for JSON and other formats. 5 | 6 | 7 | ## Heroku Postgres 8 | Heroku provides a Posgresql service (free 10,000 rows limit per database) which provisions PostgreSQL databases on demand, so is a simple way to start development. 9 | 10 | ![Heroku Postgres Features](https://raw.githubusercontent.com/jr0cket/developer-guides/gh-pages/heroku-postgres-features-concept.png) 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /overrides/404.html: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | {% extends "main.html" %} 7 | 8 | 9 | {% block content %} 10 |

This is not the page you are looking for

11 | 12 |

13 | Sorry we have arrived at a page that does not exist... 14 |

15 | 16 |

17 | Practicalli website are published using Material for MkDocs 18 |

19 | 20 |

21 | Use the Search bar at the top of the page or left navigation to find the relevant content. 22 |

23 | 24 |

25 | 26 | Practicalli Web Services book cover 28 | 29 |

30 | 31 |

32 | 33 | Practicalli Web Services book cover 35 | 36 |

37 | 38 | 39 | {% endblock %} 40 | -------------------------------------------------------------------------------- /overrides/main.html: -------------------------------------------------------------------------------- 1 | 7 | 8 | {% extends "base.html" %} 9 | 10 | 11 | {% block announce %} 12 | 13 | Practicalli Clojure Web Services has changed to MkDocs and undergoing a re-write 14 | 15 | {% endblock %} 16 | -------------------------------------------------------------------------------- /overrides/partials/palette.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 | {% for option in config.theme.palette %} 7 | {% set scheme = option.scheme | d("default", true) %} 8 | {% set primary = option.primary | d("indigo", true) %} 9 | {% set accent = option.accent | d("indigo", true) %} 10 | 25 | {% if option.toggle %} 26 | 34 | {% endif %} 35 | {% endfor %} 36 |
37 | -------------------------------------------------------------------------------- /overrides/partials/source.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | {% set icon = config.theme.icon.repo or "fontawesome/brands/git-alt" %} 4 | {% include ".icons/" ~ icon ~ ".svg" %} 5 |
6 |
7 | {{ config.repo_name }} 8 |
9 |
10 | -------------------------------------------------------------------------------- /work-in-progress.md: -------------------------------------------------------------------------------- 1 | # Work in Progress 2 | 3 | ## Variable tag names 4 | 5 | I have slightly variable html tag content based on some config being passed into a reagent component. I want to output "adjacent" divs with an incrementing index value on the end of the class name. 6 | 7 | ```clojure 8 | [:div.row-data 9 | (for [heading column-headings] 10 | (let [heading-tag-with-class (str "div.column-heading.column-heading-" (count (:display-names heading)))] 11 | [(keyword heading-tag-with-class) {:key (:symbol heading)} 12 | ``` 13 | 14 | This solution works, is a better way?. 15 | 16 | Dominic 17 | ```clojure 18 | [:div.column-heading 19 | {:class (str "column-heading-" (count (:display-names heading)))}] 20 | ``` 21 | --------------------------------------------------------------------------------