caddy-xtemplate
is a Caddy module that extends
Go's html/template
library to be capable
enough to host an entire server-side application in it. Designed with the
htmx.org js library in mind, which makes server side
rendered sites feel as interactive as a Single Page Apps.
⚠️ This project is in active development, expect regular breaking changes.⚠️
- ✨ Features
- 🐾 Showcase
- 🏃 Quickstart
- 👓 Config
- 💼 Template syntax
- 🛠️ Development
- ✅ License
<ul>
{{range .Query `SELECT id,name FROM contacts`}}
<li><a href="/contact/{{.id}}">{{.name}}</a></li>
{{end}}
</ul>
Note: The html/template library automatically sanitizes inputs, so you can rest easy from basic XSS attacks. Note: if you generate some html that you do trust, it's easy to inject if you intend to.
<html>
<title>Home</title>
<!-- import the contents of a file -->
{{template "/shared/_head.html" .}}
<body>
<!-- invoke a custom template defined anywhere -->
{{template "navbar" .}}
...
</body>
</html>
GET
requests for any file will invoke the template file at that path. Except
files that start with _
which are not routed, this lets you define templates
that only other templates can invoke.
.
├── index.html GET /
├── todos.html GET /todos
├── admin
│ └── settings.html GET /admin/settings
└── shared
└── _head.html (not routed)
Create custom route handlers by defining a template with a name matching the
pattern <method> <path>
. Use
httprouter syntax for path
parameters and wildcards, which are made available in the template as values in
the .Param
key while serving a request.
<!-- match on path parameters -->
{{define "GET /contact/:id"}}
{{$contact := .QueryRow `SELECT name,phone FROM contacts WHERE id=?`
(.Params.ByName "id")}}
<div>
<span>Name: {{.name}}</span>
<span>Phone: {{.phone}}</span>
</div>
<!-- match on any http method -->
{{end}} {{define "DELETE /contact/:id"}}
{{$_ := .Exec `DELETE from contacts WHERE id=?` (.Params.ByName "id")}} OK
{{end}}
Templates are reloaded and validated automatically as soon as they are modified, no need to restart the server. If there's a syntax error it continues to serve the old version and prints the loading error out in Caddy's logs.
Ctrl+S > Alt+Tab > F5
- infogulch/xrss, an rss feed reader built with htmx and tailwindcss.
- infogulch/todos, a demo todomvc application.
Download caddy with all standard modules, plus the xtemplate
module (!important)
from Caddy's build and download server:
https://caddyserver.com/download?package=github.com%2Finfogulch%2Fcaddy-xtemplate
Write your caddy config and use the xtemplate http handler:
:8080
route {
xtemplate {
template_root templates
}
}
Write .html
files in the root directory specified in your Caddy config.
Run caddy with your config: caddy run --config Caddyfile
Remember Caddy is a super http server, check out the caddy docs for features you may want to layer on top. Examples: serving static files (css/js libs), set up an auth proxy, caching, rate limiting, automatic https, and more!
xtemplate is configured through caddy's configuration system. Here are the config fields available to a Caddyfile:
xtemplate {
template_root <root directory where template files are loaded>
context_root <root directory that template funcs have access to>
delimiters <left> <right> # defaults: {{ and }}
database { # default empty, no db available
driver <driver> # driver and connstr are passed directly to sql.Open
connstr <connection string> # check your sql driver for connstr details
}
config { # a map of configs, accessible in the template as .Config
key1 value1
key2 value2
}
funcs_modules <mod1> <mod2> # a list of caddy modules under the `xtemplate.funcs.*`
# namespace that implement the FuncsProvider interface,
# to add custom funcs to the Template FuncMap.
}
These sql drivers are available by default. Modify db.go and recompile xtemplate to include your preferred drivers.
- mattn/sqlite3 (requires building with
CGO_ENABLED=1
, not available from the caddy build server) - cznic/sqlite (available without CGo, including from the caddy build server)
Template syntax uses Go's html/template
module, and extends it with custom functions and useful context.
The dot context {{.}}
set on the main template handler provides
request-specific data and stateful actions. See tplcontext.go
for details.
- Request and response related fields and fields
.Req
is the current HTTP request struct, http.Request, which has various fields, including:.Method
- the method.URL
- the URL, which in turn has component fields (Scheme, Host, Path, etc.).Header
- the header fields.Host
- the Host or :authority header of the request
.OriginalReq
is the original, unmodified, un-rewritten request as it originally came in over the wire. Has the same fields as.Req
..Params
is a list of path parameters extracted from the url based on the current route, see custom routes.{{.Params.ByName "id"}}
.RemoteIP
is the client's IP address.{{.RemoteIP}}
.Host
is the hostname portion (no port) of the Host header of the HTTP request..Cookie
Gets the value of a cookie by name.{{.Cookie "cookiename"}}
.Placeholder
gets a caddy "placeholder variable". The braces ({}
) have to be omitted..RespStatus
Set the status code of the current response.{{.RespStatus 201}}
.RespHeader.Add
Adds a header field to the HTTP response.{{.RespHeader.Add "Field-Name" "val"}}
.RespHeader.Set
Sets a header field on the HTTP response, replacing any existing value..RespHeader.Del
Deletes a header field on the HTTP response.
- File related funcs. File operations are rooted at the directory specified by the
root
config option..ReadFile
reads and returns the contents of another file, as-is. Note that the contents are NOT escaped, so you should only read trusted files..ListFiles
returns a list of the files in the given directory, which is relative to the template context's file root..FileExists
returns true if filename can be opened successfully.StatFile
returns Stat of a filename.
- Database related funcs. All funcs accept a query string and any number of parameters. Prefer using parameters over building the query string dynamically.
.Exec
executes a statment.QueryRows
executes a query and returns all rows in a big[]map[string]any
..QueryRow
executes a query, which must return one row, and returns themap[string]any
..QueryVal
executes a query, which must return one row and one column, and returns the value of the column.
- Other
.Template
evaluate the template name with the given context and return the result as a string..Funcs
returns a list of all the custom FuncMap funcs that are available to call. Useful in combination with thetry
func..Config
is a map of config strings set in the Caddyfile. See Config.
There are built-in functions that perform actions that are unrelated to a specific request. See funcs.go for details.
Expand for a stdlib funcs documentation.
and
Returns the boolean AND of its arguments by returning the first empty argument or the last argument. That is, "and x y" behaves as "if x then y else x." Evaluation proceeds through the arguments left to right and returns when the result is determined.call
Returns the result of calling the first argument, which must be a function, with the remaining arguments as parameters. Thus "call .X.Y 1 2" is, in Go notation, dot.X.Y(1, 2) where Y is a func-valued field, map entry, or the like. The first argument must be the result of an evaluation that yields a value of function type (as distinct from a predefined function such as print). The function must return either one or two result values, the second of which is of type error. If the arguments don't match the function or the returned error value is non-nil, execution stops.html
Returns the escaped HTML equivalent of the textual representation of its arguments. This function is unavailable in html/template, with a few exceptions.index
Returns the result of indexing its first argument by the following arguments. Thus "index x 1 2 3" is, in Go syntax, x[1][2][3]. Each indexed item must be a map, slice, or array.slice
slice returns the result of slicing its first argument by the remaining arguments. Thus "slice x 1 2" is, in Go syntax, x[1:2], while "slice x" is x[:], "slice x 1" is x[1:], and "slice x 1 2 3" is x[1:2:3]. The first argument must be a string, slice, or array.js
Returns the escaped JavaScript equivalent of the textual representation of its arguments.len
Returns the integer length of its argument.not
Returns the boolean negation of its single argument.or
Returns the boolean OR of its arguments by returning the first non-empty argument or the last argument, that is, "or x y" behaves as "if x then x else y". Evaluation proceeds through the arguments left to right and returns when the result is determined.print
An alias for fmt.Sprintprintf
An alias for fmt.Sprintfprintln
An alias for fmt.Sprintlnurlquery
Returns the escaped value of the textual representation of its arguments in a form suitable for embedding in a URL query. This function is unavailable in html/template, with a few exceptions.
See the Sprig documentation for details: Sprig Function Documentation.
Expand for a listing of Sprig funcs.
- String Functions:
trim
,trimAll
,trimSuffix
,trimPrefix
,repeat
,substr
,replace
,shuffle
,nospace
,trunc
,abbrev
,abbrevboth
,wrap
,wrapWith
,quote
,squote
,cat
,indent
,nindent
upper
,lower
,title
,untitle
,camelcase
,kebabcase
,swapcase
,snakecase
,initials
,plural
contains
,hasPrefix
,hasSuffix
randAlphaNum
,randAlpha
,randNumeric
,randAscii
regexMatch
,mustRegexMatch
,regexFindAll
,mustRegexFindAll
,regexFind
,mustRegexFind
,regexReplaceAll
,mustRegexReplaceAll
,regexReplaceAllLiteral
,mustRegexReplaceAllLiteral
,regexSplit
,mustRegexSplit
,regexQuoteMeta
- String List Functions:
splitList
,sortAlpha
, etc.
- Integer Math Functions:
add
,max
,mul
, etc.- Integer Slice Functions:
until
,untilStep
- Integer Slice Functions:
- Float Math Functions:
addf
,maxf
,mulf
, etc. - Date Functions:
now
,date
, etc. - Defaults Functions:
default
,empty
,coalesce
,fromJson
,toJson
,toPrettyJson
,toRawJson
,ternary
- Encoding Functions:
b64enc
,b64dec
, etc. - Lists and List Functions:
list
,first
,uniq
, etc. - Dictionaries and Dict Functions:
get
,set
,dict
,hasKey
,pluck
,dig
,deepCopy
, etc. - Type Conversion Functions:
atoi
,int64
,toString
, etc. - Path and Filepath Functions:
base
,dir
,ext
,clean
,isAbs
,osBase
,osDir
,osExt
,osClean
,osIsAbs
- Flow Control Functions:
fail
- Advanced Functions
- UUID Functions:
uuidv4
- OS Functions:
env
,expandenv
- Version Comparison Functions:
semver
,semverCompare
- Reflection:
typeOf
,kindIs
,typeIsLike
, etc. - Cryptographic and Security Functions:
derivePassword
,sha256sum
,genPrivateKey
, etc. - Network:
getHostByName
- UUID Functions:
markdown
Renders the given Markdown text as HTML and returns it. This uses the Goldmark library, which is CommonMark compliant. It also has these extensions enabled: Github Flavored Markdown, Footnote, and syntax highlighting provided by Chroma.splitFrontMatter
Splits front matter out from the body. Front matter is metadata that appears at the very beginning of a file or string. Front matter can be in YAML, TOML, or JSON formats..Meta
to access the metadata fields, for example:{{$parsed.Meta.title}}
.Body
to access the body after the front matter, for example:{{markdown $parsed.Body}}
sanitizeHtml
Uses bluemonday to sanitize strings with html content.{{sanitizeHtml "strict" "Shows <b>only</b> text content"}}
- First parameter is the name of the chosen sanitization policy.
"strict"
=StrictPolicy()
,"ugc"
=UGCPolicy()
for 'user generated content',"externalugc"
=UGCPolicy()
+ disallow relative urls + add target=_blank to urls. - Second parameter is the content to sanitize.
- Returns the string as a
template.HTML
type which can be output directly into the document withouttrustHtml
.
- First parameter is the name of the chosen sanitization policy.
humanize
Transforms size and time inputs to a human readable format using the go-humanize library. Call with two parameters, the format type and the value to format. Format types are:- size which turns an integer amount of bytes into a string like
2.3 MB
, for example:{{humanize "size" "2048000"}}
- time which turns a time string into a relative time string like
2 weeks ago
, for example:{{humanize "time" "Fri, 05 May 2022 15:04:05 +0200"}}
- size which turns an integer amount of bytes into a string like
ksuid
returns a 'K-Sortable Globally Unique ID' using segmentio/ksuididx
gets an item from a list, similar to the built-inindex
, but with reversed args: index first, then array. This is useful to use index in a pipeline, for example:{{generate-list | idx 5}}
try
takes a function that returns an error in the first argument and calls it with the values from the remaining arguments, and returns the result including any error as struct fields. This enables template authors to handle funcs that return errors within the template definition. Example:{{ $result := try .QueryVal "SELECT 'oops' WHERE 1=0" }}{{if $result.OK}}{{$result.Value}}{{else}}QueryVal requires exactly one row. Error: {{$result.Error}}{{end}}
To work on this project, install xcaddy
, then build from the repo root:
# build a caddy executable with the latest version of caddy-xtemplate from github:
xcaddy build --with github.com/infogulch/caddy-xtemplate
# build a caddy executable and override the xtemplate module with your
# modifications in the current directory:
xcaddy build --with github.com/infogulch/caddy-xtemplate=.
# build with CGO in order to use the sqlite3 db driver
CGO_ENABLED=1 xcaddy build --with github.com/infogulch/caddy-xtemplate
# build enable the sqlite_json build tag to get json funcs
GOFLAGS='-tags="sqlite_json"' CGO_ENABLED=1 xcaddy build --with github.com/infogulch/caddy-xtemplate
TZ=UTC git --no-pager show --quiet --abbrev=12 --date='format-local:%Y%m%d%H%M%S' --format="%cd-%h"
The idea for this project started in infogulch/go-htmx (now archived), which included the first implementations of template-name-based routing, exposing sql db functions to templates, and a persistent templates instance shared across requests and reloaded when template files changed.
go-htmx was refactored and rebased on top of the templates module from the
Caddy server to create caddy-xtemplate
in order to get a jump start on
broader web server features without having to implement them from scratch.
caddy-xtemplate
is licensed under the Apache 2.0 license. See LICENSE