Learn how to serve static files and use templating libraries like Selmer and Hiccup to create dynamic HTML content in Clojure web applications.
In the realm of web development, serving static content and generating dynamic HTML pages are fundamental tasks. As a Java engineer venturing into Clojure, you’ll find that Clojure offers elegant solutions for both. This section will guide you through serving static files using middleware and creating dynamic views with templating libraries such as Selmer and Hiccup. We’ll also explore best practices for organizing templates and ensuring security against common web vulnerabilities like Cross-Site Scripting (XSS).
Static files such as CSS, JavaScript, and images are integral to web applications. In Clojure, serving these files efficiently can be achieved using middleware. Middleware acts as an intermediary layer that processes requests and responses, making it ideal for handling static content.
Ring, a popular Clojure web application library, provides a straightforward way to serve static files. The ring.middleware.resource
middleware is commonly used for this purpose. It allows you to specify a directory from which static files will be served.
Here’s an example of how to set up a Ring application to serve static files:
(ns myapp.core
(:require [ring.adapter.jetty :refer [run-jetty]]
[ring.middleware.resource :refer [wrap-resource]]
[ring.middleware.defaults :refer [wrap-defaults site-defaults]]))
(defn app [request]
{:status 200
:headers {"Content-Type" "text/html"}
:body "<h1>Welcome to My Clojure Web App</h1>"})
(defn -main []
(run-jetty (wrap-defaults (wrap-resource app "public") site-defaults)
{:port 3000}))
In this setup, the wrap-resource
middleware is used to serve static files from the public
directory. When a request is made for a static file, such as /css/style.css
, the middleware will look for the file in the public/css
directory and serve it if found.
To keep your project organized, it’s a good practice to structure your static files in a clear directory hierarchy. A common convention is to have a public
directory at the root of your project, with subdirectories for different types of static content:
myapp/
├── src/
│ └── myapp/
│ └── core.clj
├── public/
│ ├── css/
│ │ └── style.css
│ ├── js/
│ │ └── app.js
│ └── images/
│ └── logo.png
└── resources/
Dynamic web pages require templating systems to generate HTML content. Clojure offers several templating libraries, with Selmer and Hiccup being among the most popular.
Selmer is a templating engine for Clojure inspired by Django’s template language. It allows you to create templates with familiar syntax and supports features like variable interpolation and control structures.
To use Selmer, add it to your project dependencies:
:dependencies [[selmer "1.12.40"]]
Selmer templates are typically stored in the resources/templates
directory. Here’s an example of a simple Selmer template:
<!-- resources/templates/welcome.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Welcome</title>
<link rel="stylesheet" href="/css/style.css">
</head>
<body>
<h1>Welcome, {{name}}!</h1>
{% if logged_in %}
<p>You are logged in as {{username}}.</p>
{% else %}
<p>Please log in to access more features.</p>
{% endif %}
</body>
</html>
To render a Selmer template, use the selmer.parser/render-file
function:
(ns myapp.core
(:require [selmer.parser :refer [render-file]]))
(defn render-welcome-page [name logged-in username]
(render-file "templates/welcome.html"
{:name name
:logged_in logged-in
:username username}))
This function takes the template file path and a map of variables to interpolate into the template. It returns the rendered HTML as a string.
Hiccup is another popular templating library that allows you to generate HTML using Clojure data structures. It provides a more programmatic approach compared to Selmer’s template files.
With Hiccup, HTML elements are represented as Clojure vectors. Here’s an example of generating a simple HTML page:
(ns myapp.core
(:require [hiccup.page :refer [html5 include-css]]))
(defn welcome-page [name logged-in username]
(html5
[:head
[:title "Welcome"]
(include-css "/css/style.css")]
[:body
[:h1 (str "Welcome, " name "!")]
(if logged-in
[:p (str "You are logged in as " username ".")]
[:p "Please log in to access more features."])]))
In this example, html5
is a helper function that generates the HTML5 doctype and basic structure. The include-css
function is used to include a CSS file.
Dynamic views are essential for interactive web applications. Both Selmer and Hiccup support variable interpolation and control structures, allowing you to create rich, dynamic content.
Variable interpolation is the process of inserting variable values into a template. In Selmer, this is done using the {{variable}}
syntax, while in Hiccup, you can directly use Clojure expressions.
Control structures like conditionals and loops are crucial for dynamic content. Selmer supports these with {% if %}
, {% else %}
, and {% for %}
tags. Hiccup, being a Clojure library, uses standard Clojure control structures like if
and for
.
Here’s an example of using a loop in a Selmer template:
<!-- resources/templates/items.html -->
<ul>
{% for item in items %}
<li>{{item}}</li>
{% endfor %}
</ul>
And the equivalent in Hiccup:
(defn items-list [items]
[:ul
(for [item items]
[:li item])])
As your application grows, organizing templates becomes crucial for maintainability. A common practice is to use partials—reusable template fragments that can be included in other templates.
Selmer supports partials through the {% include %}
tag. Here’s how you might use a partial for a navigation bar:
<!-- resources/templates/navbar.html -->
<nav>
<ul>
<li><a href="/">Home</a></li>
<li><a href="/about">About</a></li>
<li><a href="/contact">Contact</a></li>
</ul>
</nav>
<!-- resources/templates/base.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{{title}}</title>
<link rel="stylesheet" href="/css/style.css">
</head>
<body>
{% include "navbar.html" %}
<div class="content">
{{content}}
</div>
</body>
</html>
In Hiccup, you can create reusable functions for partials:
(defn navbar []
[:nav
[:ul
[:li [:a {:href "/"} "Home"]]
[:li [:a {:href "/about"} "About"]]
[:li [:a {:href "/contact"} "Contact"]]]])
(defn base-page [title content]
(html5
[:head
[:title title]
(include-css "/css/style.css")]
[:body
(navbar)
[:div.content content]]))
Security is paramount when generating HTML content dynamically. Cross-Site Scripting (XSS) is a common vulnerability that occurs when untrusted data is included in web pages without proper validation or escaping.
Both Selmer and Hiccup provide mechanisms to escape user input, preventing XSS attacks.
|safe
filter, but use it cautiously.Serving static content and generating dynamic HTML pages are essential skills for building robust web applications in Clojure. By leveraging middleware for static files and templating libraries like Selmer and Hiccup, you can create efficient, secure, and maintainable web applications. Remember to organize your templates for reuse and prioritize security to protect your application from vulnerabilities.