A modest template to build dynamic web apps in Go, HTML and sprinkles and spots of javascript.
- Build dynamic websites using the tools you already know(Go, HTML, CSS, Vanilla Javascript) for the most part.
- Use bulma components to speed up prototyping a responsive and good-looking UI.
- Use turbo & stimulusjs for most of the interactivity.
- For really complex interactivity use Svelte for a single div in a few spots.
- Lightweight and productive. Fast development cycle.
- Easy to start, easy to maintain.
For a more complete implementation using this technique please see gomodest-starter.
- Use as a github template
git clone https://github.com/<user>/<mytemplate>andcd /path/to/your/gomodest-templatemake watch(starts hot-reload for go, html and javascript changes)- open localhost:3000.
or
brew install gh
gh repo create myapp --template adnaan/gomodest-template
cd myapp
make install # or (make install-x64)
# replace gomodest-template with your app name
go get github.com/piranha/goreplace
$(go env GOPATH)/bin/goreplace gomodest-template -r myapp
git add . && git commit -m "replace gomodest-template"
make watch # or make watch-x64- Folder Structure
- Views using html templates
- Interactivity using Javascript
- Styling and Images
- Samples
- Dependencies
Table of contents generated with markdown-toc
-
templates/
- layouts/
- partials/
- list of view files
-
assets/
-
images/
-
src/
- components/
- controllers/
- index.js
- styles.scss
-
-
templatesis the root directory where all html/templates assets are found. -
layoutscontains the layout files. Layout is a container forpartialsandview files -
partialscontains the partial files. Partial is a reusable html template which can be used in one of two ways:-
Included in a
layoutfile:{{include "partials/header"}} -
Included in a
viewfile:{{template "main" .}}. When used in a view file, a partial must be enclosed in adefinetag:{{define "main"}} Hello {{.hello}} {{end}}
-
-
viewfiles are put in the root of thetemplatesdirectory. They are contained within alayoutmust be enclosed in adefine contenttag:{{define "content"}} App's {{.dashboard}} {{end}}Viewis rendered within alayout:indexLayout, err := rl.New( rl.Layout("index"), rl.DisableCache(true), rl.DefaultHandler(func(w http.ResponseWriter, r *http.Request) (rl.M, error) { return rl.M{ "app_name": "gomdest-template", }, nil })) ... r.Get("/", indexLayout.Handle("home", func(w http.ResponseWriter, r *http.Request) (rl.M, error) { return rl.M{ "hello": "world", }, nil }))
Here the
view:homeis rendered within theindexlayout.
Please see the templates directory.
-
assetsdirectory contains the public asset pipeline for the project.styles.scssis a customscssfile for [bulma][https://bulma.io] as documented here.index.jsis the entrypoint for loadingstimulusjscontrollers sourced from this [example](https://github.com/hotwired/stimulus-starter.controllerscontains stimulusjs controllers.componentscontains single file svelte components.
There are three kinds of html/template files in this project.
layout: defines the base structure of a web page.partial: reusable snippets of html. It can be of two types:layout partials&view partials.view: the main container for the web page logic contained within alayout. It must be enclosed in adefine contenttemplate definition. It can use view partials.
Create header.html file in templates/partials.
<meta charset="UTF-8">
<meta name="description" content="A modest way to build golang web apps">
<meta name="viewport"content="width=device-width, initial-scale=1.0, maximum-scale=5.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
...Create index.html file in templates/layouts and use the above partial.
<!DOCTYPE html>
<html lang="en">
<head>
<title>{{.app_name}}</title>
{{include "partials/header"}}
</head>
<body ...>
...
</body>
</html>Create main.html in templates/partials
{{define "main"}}
<main>
<div class="columns is-centered is-vcentered is-mobile py-5">
<div class="column is-narrow" style="width: 70%">
<h1 class="has-text-centered title">Hello {{.hello}}</h1>
</div>
</div>
</main>
{{end}}This is a different from the layout partial since it's closed in a define tag.
Create home.html in templates and use the above partial.
{{define "content"}}
<div class="columns is-mobile is-centered">
<div class="column is-half-desktop">
{{template "main" .}}
</div>
</div>
{{end}}Notice that a view is always enclosed in define content template definition.
To render the view with data we use a wrapper over the html/template package.
r.Get("/", indexLayout.Handle("home",
func(w http.ResponseWriter, r *http.Request) (rl.M, error) {
return rl.M{
"hello": "world",
}, nil
}))To learn more about html/template, please look into this amazing cheatsheet.
Reference:
templates/layout/index.htmltemplates/partials/header.htmltemplates/partials/main.htmltemplates/home.htmlmain.go
For client-side interactivity we use a bit of javascript.
A stimulus controller is a snippet of javascript which handles a single aspect of interactivity. To add a new svelte component:
Create a file with suffix: _controller.js
util_controller.js
import { Controller } from "stimulus"
export default class extends Controller {
...
connect(){
}
goto(e){
if (e.currentTarget.dataset.goto){
window.location = e.currentTarget.dataset.goto;
}
}
goback(e){
window.history.back();
}
...
}
See complete implementation in assets/src/controller/util_controller.js. To understand how stimulus works, please see the handbook.
<body data-controller="util svelte"
data-action="keydown@window->util#keyDown "
data-util-active-class="is-active">
...
<button class="button"
data-action="click->util#goto"
data-goto="/">Home
</button>
</body>Here we are attaching two controllers to the body itself since they are used often. Later we can add action and data attributes to use them.
Reference:
templates/layout/index.htmltemplates/404.htmlassets/src/controllers/util_controller.js
A svelte component is loaded into the targeted div by a stimulujs controller: controllers/svelte_controller.js. This is hooked by declaring data attributes on the div which is to be contain the svelte component:
-
data-svelte-target: Value is required to becomponent. It's used for identifying the divs as targets for thesvelte_controller. -
data-component-name: The name of the component as exported insrc/components/index.jsimport app from "./App.svelte" // export other components here. export default { app: app, }
-
data-component-props: A string map object which is passed as initial props to the svelte component.
To add a new svelte component:
{{define "content"}}
<div class="columns is-mobile is-centered">
<div class="column is-half-desktop">
<div
data-svelte-target="component"
data-component-name="app"
data-component-props="{{.Data}}">
</div>
</div>
</div>
</div>
{{end}}- Create a new svelte component in
src/componentsand export it insrc/components/index.js
import app from "./App.svelte"
// export other components here.
export default {
app: app,
}The controllers/svelte_controller.js controller loads the svelte component in to the div with the required data attributes shown in step 1.
It's possible to hydrate initial props from the server and pass onto the component. This is done by templating a string data object into the data-component-props attribute.
r.Get("/app", indexLayout.Handle("app",
func(w http.ResponseWriter, r *http.Request) (rl.M, error) {
appData := struct {
Title string `json:"title"`
}{
Title: "Hello from server for the svelte component",
}
d, err := json.Marshal(&appData)
if err != nil {
return nil, fmt.Errorf("%v: %w", err, fmt.Errorf("encoding failed"))
}
return rl.M{
"Data": string(d), // notice struct data is converted into a string
}, nil
}))Reference:
templates/app.htmlsrc/controllers/svelte_controller.jssrc/components/*main.go
Bulma is included by default. Bulma is a productive css framework with prebuilt components and helper utilities.
assets/src/styles.scss: to override default bulma variables.webpackbundles and copies css assets to `public/assets/css.assets/images: put image assets here. it will be auto-copied topublic/assets/imagesbywebpack.
Go to localhost:3000/samples to a list of sample views. Copy-paste at will from the templates/samples directory.
