Go Website Boilerplate Template
This template is a starting point for my Go web projects including my prefered technologies described below.
While it bends to my preferences, it is designed to be adaptable, expandable and easy to set up and modify to your needs.
As of v0.1.0, it is also my first Go project, so it may not be perfect and major overhauls may happen.
Contributions are welcome, but please keep in mind that this is a template, not a framework.
Features
- Go webserver with Gin
- HTMX with Go templates
- HTMX WebSocket support
- API handlers
- PostgreSQL with sqlx
- Tailwind CSS (compiled on startup)
- Database migration system
- Dockerfile with multi-stage build
- Demo of all features
Template Setup
After cloning the template, to finalize the setup, follow these quick steps:
-
Clone your repo:
Replace [YourUsername]
and [YourRepoName]
with your GitHub username and repository name.
git clone https://github.com/[YourUsername]/[YourRepoName].git
cd [YourRepoName]
-
Run the setup script:
This script essentially renames the project to your repository name.
python repo-template-setup.py
-
Commit and push:
git add -A
git commit -m "Finalize setup"
git push
That's it! Your repo is now ready to use.
Requirements
Recommended VSCode Extensions
Try it out
To try the demo, you need to set up a database and .env, as the demo relies on it.
cp .env.example .env
Then edit the .env file to match your database settings.
Your .env
file must be set up, since it will use the same variables for the app as setting up the database in docker.
docker compose -f docker-compose.dev-db.yml up
You can now build the application or run the release version:
go build
./GoWebsite-Boilerplate
View the demo at http://localhost:8080
Development
Change the layout
The base templateof the application is defined in the templates/layouts/default_base.html
file.
You can change this as needed, but mind the imports and templating to maintain all functionality.
Creating a page
Create a new html file in the templates
directory
Add the following to any page template:
{{ template "default_base.html" . }}
{{ define "content" }}
Your page content should go here
{{ end }}
Create a handler for the page in ./handlers
with this structure:
var db *sqlx.DB
// HomePageHandler Implements types.HandlerRegistrar interface
type HomePageHandler struct{}
// Initialize is called before the handler is registered
func (h *HomePageHandler) Initialize(initContext *types.HandlerInitContext) {
db = initContext.DB
}
// Implements PageRouteRegistrar interface
func (h *HomePageHandler) Handler(engine *gin.Engine) {
engine.GET("/", h.get)
}
func (h *HomePageHandler) get(c *gin.Context) {
// Set SEO meta data
meta := &page.PageMetaData{
Title: "Demo Home Page",
Description: "This is a demo home page showing off the boilerplate.",
}
// Any additional data required by the page
data := &map[string]interface{}{
"Counter": CounterData{
Value: 0,
Color: "#fff",
},
}
// Turn it into structured data
structuredData := page.StructurePageData(&data, meta)
// Render page
c.HTML(http.StatusOK, "home_page.html", structuredData)
}
You can register multiple related handlers in one file. For more details see the Gin documentation
Register the handler in ./handlers/registration.go
:
Add the handler to the RouteHandlers
slice:
&yourmodule.SomePageHandler{},
Creating an HTMX component
The instructions are the same as for creating a page, but you want to add the route in the relevant page or system's handler file.
Additionally, htmx component templates should not include {{ template "default_base.html" . }}
.
You can still use page.StructurePageData(data, nil)
without defining meta for between page and component, if the page loads the component as well.
API Handlers
They are registered in ./handlers/registration.go
alongside page handlers.
API handlers should return JSON data. Included in the package is a structured API response outputting responses like so:
{
"success": true,
"data": {
"someData": "Some data"
}
}
or for errors:
{
"success": false,
"error": "Error message"
}
You can generate this data using common.ApiResponseFactory.Ok(data)
and common.ApiResponseFactory.Error(err)
respectively.
An API handler will generally look like this:
var db *sqlx.DB
// HomeApiHandler Implements types.HandlerRegistrar interface
type HomeApiHandler struct{}
// HomePageData is the response for the time call
type HomePageData struct {
Time string `json:"time"`
}
// Initialize is called before the handler is registered
func (h *HomeApiHandler) Initialize(initContext *types.HandlerInitContext) {
db = initContext.DB
}
func (h *HomeApiHandler) Handler(engine *gin.Engine) {
engine.GET("/api/home/get-server-time", h.get)
}
func (h *HomeApiHandler) get(c *gin.Context) {
timeStr := time.Now().Format("2006-01-02 15:04:05")
resp := common.ApiResponseFactory.Ok(
&HomePageData{Time: timeStr})
// Render page
c.JSON(http.StatusOK, resp)
}
Static and dynamic assets
These are files that are served directly by the webserver in slightly different ways.
Static Assets
- Repo path:
./assets/static/
- Web path:
/
- Embedded: Yes
- Ideal for: Small, commonly used files
Static assets are embedded in the executable, this means they can be served faster, but they cannot be changed without recompiling the application.
It also means it's not suitable for large files.
It's ideal for small files like favicons, logos, javascript libraries, etc.
They each get their own handler registered directly at /
.
Dynamic Assets
- Repo path:
./assets/dynamic/
- Web path:
/dynamic/
- Embedded: No
- Ideal for: Large files, files that change often
Dynamic assets are not embedded in the executable, this means they can be changed without recompiling the application, but they are served slightly slower.
It's ideal for large files like images, videos, etc. assuming they are not hosted on a CDN.
They do not have direct handlers, but instead get served as fallback before the 404 handler is called.
We also use this to serve compiled Tailwind files, since they are generated on application startup.
You can change this behavior in by setting up your own pre-compile postcss call and disabling config.MinifyCss
in config/config.go
.
Default Template Definitions
These ensure that all pages have the required blocks and that the layout is loaded correctly.
You can add additional blocks to the layout as needed in config/template_definitions.go
.
The default definitions are:
content
: Each page expects a content
block to be defined. HTMX components do not use this block.
scripts
: Optional, at the bottom of the page. Can be used to add additional scripts to the page. <script>
tags are required.
head
: Optional, will be loaded in <head>
of the page.
page_title
: Will be used as the page title. Defaults to website name.
Utility Functions