diff --git a/examples/frontend-auth/README.md b/examples/frontend-auth/README.md
new file mode 100644
index 00000000..de588f59
--- /dev/null
+++ b/examples/frontend-auth/README.md
@@ -0,0 +1,13 @@
+# reitit-frontend + controllers example
+
+## Usage
+
+```clj
+> lein figwheel
+```
+
+Go with browser to http://localhost:3449
+
+## License
+
+Copyright © 2018 Metosin Oy
diff --git a/examples/frontend-auth/checkouts/reitit-core b/examples/frontend-auth/checkouts/reitit-core
new file mode 120000
index 00000000..a59d247e
--- /dev/null
+++ b/examples/frontend-auth/checkouts/reitit-core
@@ -0,0 +1 @@
+../../../modules/reitit-core
\ No newline at end of file
diff --git a/examples/frontend-auth/checkouts/reitit-frontend b/examples/frontend-auth/checkouts/reitit-frontend
new file mode 120000
index 00000000..20cdd448
--- /dev/null
+++ b/examples/frontend-auth/checkouts/reitit-frontend
@@ -0,0 +1 @@
+../../../modules/reitit-frontend
\ No newline at end of file
diff --git a/examples/frontend-auth/checkouts/reitit-schema b/examples/frontend-auth/checkouts/reitit-schema
new file mode 120000
index 00000000..a68c7f05
--- /dev/null
+++ b/examples/frontend-auth/checkouts/reitit-schema
@@ -0,0 +1 @@
+../../../modules/reitit-schema
\ No newline at end of file
diff --git a/examples/frontend-auth/project.clj b/examples/frontend-auth/project.clj
new file mode 100644
index 00000000..6a4098de
--- /dev/null
+++ b/examples/frontend-auth/project.clj
@@ -0,0 +1,54 @@
+(defproject frontend "0.1.0-SNAPSHOT"
+ :description "FIXME: write description"
+ :url "http://example.com/FIXME"
+ :license {:name "Eclipse Public License"
+ :url "http://www.eclipse.org/legal/epl-v10.html"}
+
+ :dependencies [[org.clojure/clojure "1.10.0"]
+ [ring-server "0.5.0"]
+ [reagent "0.8.1"]
+ [ring "1.7.1"]
+ [compojure "1.6.1"]
+ [hiccup "1.0.5"]
+ [org.clojure/clojurescript "1.10.439"]
+ [metosin/reitit "0.2.13"]
+ [metosin/reitit-schema "0.2.13"]
+ [metosin/reitit-frontend "0.2.13"]
+ ;; Just for pretty printting the match
+ [fipp "0.6.14"]]
+
+ :plugins [[lein-cljsbuild "1.1.7"]
+ [lein-figwheel "0.5.18"]]
+
+ :source-paths []
+ :resource-paths ["resources" "target/cljsbuild"]
+
+ :profiles {:dev {:dependencies [[binaryage/devtools "0.9.10"]]}}
+
+ :cljsbuild
+ {:builds
+ [{:id "app"
+ :figwheel true
+ :source-paths ["src"]
+ :watch-paths ["src" "checkouts/reitit-frontend/src"]
+ :compiler {:main "frontend.core"
+ :asset-path "/js/out"
+ :output-to "target/cljsbuild/public/js/app.js"
+ :output-dir "target/cljsbuild/public/js/out"
+ :source-map true
+ :optimizations :none
+ :pretty-print true
+ :preloads [devtools.preload]}}
+ {:id "min"
+ :source-paths ["src"]
+ :compiler {:output-to "target/cljsbuild/public/js/app.js"
+ :output-dir "target/cljsbuild/public/js"
+ :source-map "target/cljsbuild/public/js/app.js.map"
+ :optimizations :advanced
+ :pretty-print false}}]}
+
+ :figwheel {:http-server-root "public"
+ :server-port 3449
+ :nrepl-port 7002
+ ;; Server index.html for all routes for HTML5 routing
+ :ring-handler backend.server/handler})
diff --git a/examples/frontend-auth/resources/public/index.html b/examples/frontend-auth/resources/public/index.html
new file mode 100644
index 00000000..ce1dd45b
--- /dev/null
+++ b/examples/frontend-auth/resources/public/index.html
@@ -0,0 +1,10 @@
+
+
+
+ Reitit frontend example
+
+
+
+
+
+
diff --git a/examples/frontend-auth/src/backend/server.clj b/examples/frontend-auth/src/backend/server.clj
new file mode 100644
index 00000000..88171cfa
--- /dev/null
+++ b/examples/frontend-auth/src/backend/server.clj
@@ -0,0 +1,11 @@
+(ns backend.server
+ (:require [clojure.java.io :as io]
+ [ring.util.response :as resp]
+ [ring.middleware.content-type :as content-type]))
+
+(def handler
+ (-> (fn [request]
+ (or (resp/resource-response (:uri request) {:root "public"})
+ (-> (resp/resource-response "index.html" {:root "public"})
+ (resp/content-type "text/html"))))
+ content-type/wrap-content-type))
diff --git a/examples/frontend-auth/src/frontend/core.cljs b/examples/frontend-auth/src/frontend/core.cljs
new file mode 100644
index 00000000..17bd99e6
--- /dev/null
+++ b/examples/frontend-auth/src/frontend/core.cljs
@@ -0,0 +1,153 @@
+(ns frontend.core
+ (:require [reagent.core :as r]
+ [reitit.frontend :as rf]
+ [reitit.frontend.easy :as rfe]
+ [reitit.frontend.controllers :as rfc]
+ [reitit.coercion :as rc]
+ [reitit.coercion.schema :as rsc]
+ [schema.core :as s]
+ [fipp.edn :as fedn]))
+
+(defonce state (r/atom {:user nil}))
+
+(defn home-page []
+ [:div
+ [:h2 "Welcome to frontend"]
+ [:p "Look at console log for controller calls."]])
+
+(defn item-page [match]
+ (let [{:keys [path query]} (:parameters match)
+ {:keys [id]} path]
+ [:div
+ [:ul
+ [:li [:a {:href (rfe/href ::item {:id 1})} "Item 1"]]
+ [:li [:a {:href (rfe/href ::item {:id 2} {:foo "bar"})} "Item 2"]]]
+ (if id
+ [:h2 "Selected item " id])
+ (if (:foo query)
+ [:p "Optional foo query param: " (:foo query)])]))
+
+(defn login-done [user]
+ (swap! state assoc :user user))
+
+(defn login [user]
+ ;; In real app one would send API call here to create session or retrieve token or something
+ ;; and the callback would update app-state
+ (js/setTimeout #(login-done user) 250))
+
+(defn logout []
+ (swap! state assoc :user nil))
+
+(defn login-view []
+ (let [form (r/atom {})]
+ (fn []
+ [:div
+ [:form
+ {:on-submit (fn [e]
+ (.preventDefault e)
+ (if (and (:username @form)
+ (:password @form))
+ (login @form)))}
+
+ [:label "Username"]
+ [:input
+ {:default-value ""
+ :on-change #(swap! form assoc :username %)}]
+
+ [:label "Password"]
+ [:input
+ {:default-value ""
+ :on-change #(swap! form assoc :password %)}]
+
+ [:button
+ {:type "submit"}
+ "Login"]]])))
+
+(defn about-page []
+ [:div
+ [:p "This view is public."]])
+
+(defn main-view []
+ (let [{:keys [user match]} @state
+ route-data (:data match)]
+ [:div
+ [:ul
+ [:li [:a {:href (rfe/href ::frontpage)} "Frontpage"]]
+ [:li [:a {:href (rfe/href ::about)} "About (public)"]]
+ [:li [:a {:href (rfe/href ::item-list)} "Item list"]]
+ (if user
+ [:li [:a {:on-click (fn [e]
+ (.preventDefault e)
+ (logout))
+ :href "#"}
+ "Logout"]])]
+ ;; If user is authenticated
+ ;; or if this route has been defined as public, else login view
+ (if (or user
+ (:public? route-data))
+ (if match
+ (let [view (:view route-data)]
+ [view match]))
+ [login-view])
+ [:pre (with-out-str (fedn/pprint @state))]]))
+
+(defn log-fn [& params]
+ (fn [_]
+ (apply js/console.log params)))
+
+(def routes
+ (rf/router
+ ["/"
+ [""
+ {:name ::frontpage
+ :view home-page
+ :controllers [{:start (log-fn "start" "frontpage controller")
+ :stop (log-fn "stop" "frontpage controller")}]}]
+
+ ["about"
+ {:name ::about
+ :view about-page
+ :public? true}]
+
+ ["items"
+ ;; Shared data for sub-routes
+ {:view item-page
+ :controllers [{:start (log-fn "start" "items controller")
+ :stop (log-fn "stop" "items controller")}]}
+
+ [""
+ {:name ::item-list
+ :controllers [{:start (log-fn "start" "item-list controller")
+ :stop (log-fn "stop" "item-list controller")}]}]
+ ["/:id"
+ {:name ::item
+ :parameters {:path {:id s/Int}
+ :query {(s/optional-key :foo) s/Keyword}}
+ :controllers [{:params (fn [match]
+ (:path (:parameters match)))
+ :start (fn [params]
+ (js/console.log "start" "item controller" (:id params)))
+ :stop (fn [params]
+ (js/console.log "stop" "item controller" (:id params)))}]}]]]
+ {:data {:controllers [{:start (log-fn "start" "root-controller")
+ :stop (log-fn "stop" "root controller")}]
+ :coercion rsc/coercion
+ :public? false}}))
+
+(defn init! []
+ (rfe/start!
+ routes
+ (fn [new-match]
+ (swap! state (fn [state]
+ (if new-match
+ ;; Only run the controllers, which are likely to call authentcated APIs,
+ ;; if user has been authenticated.
+ ;; Alternative solution could be to always run controllers,
+ ;; check authentication status in each controller, or check authentication status in API calls.
+ (if (:user state)
+ (assoc state :match (assoc new-match :controllers (rfc/apply-controllers (:controllers (:match state)) new-match)))
+ (assoc state :match new-match))))))
+ {:use-fragment true})
+ (r/render [main-view] (.getElementById js/document "app")))
+
+(init!)