🔥 Big update: the Gowebly CLI now supports Templ
Tags: go, webdev, beginners, opensource
Published: 2023-11-01 11:39:41

## Introduction

Hello, everyone! 🙌

I've released a big update to the [Gowebly](https://github.com/gowebly/gowebly) CLI project in `v1.5.0` which includes [Templ][ah_templ_url] support for built-in **net/http** and all Go web frameworks: **Fiber**, **go-chi** and **echo**.

But let's not get ahead of ourselves, let's take a closer look.

<a name="toc"></a>
### 📝 Table of contents

- [What is Templ?](#what-is-templ)
- [How to use Templ with Gowebly CLI?](#how-to-use-templ-with-gowebly-cli)
- [Templates content](#templates-content)
- [Backend handler changes](#backend-handler-changes)

<a name="#what-is-templ"></a>
## What is Templ?

**Templ** is a language for writing HTML user interfaces in Go. 

{% github https://github.com/a-h/templ no-readme %}

You can use it to write code like this, which will be compiled into native Go functions to generate HTML:

```go
// hello.templ

package main

templ Hello(name string) {
  <div>Hello, { name }</div>
}

templ Greeting(person Person) {
  <div class="greeting">
    @Hello(person.Name)
  </div>
}
```

> 💡 Note: A complete user guide is here: https://templ.guide/

Create components that render fragments of HTML and compose them to create screens, pages, documents, or apps.

TOP features:
 
- Server-side rendering: Deploy as a serverless function, Docker container, or standard Go program.
- Static rendering: Create static HTML files to deploy however you choose.
- Compiled code: Components are compiled into performant Go code.
- Use Go: Call any Go code, and use standard if, switch, and for statements.
- No JavaScript: Does not require any client or server-side JavaScript.
- Great developer experience: Ships with IDE autocompletion.

Yes, Templ has plugins for most popular IDEs:

- Visual Studio Code: https://marketplace.visualstudio.com/items?itemName=a-h.templ
- VSCodium: https://open-vsx.org/extension/a-h/templ
- Vim / Neovim: https://github.com/Joe-Davidson1802/templ.vim
- Helix: https://helix-editor.com/

> 💡 Note: Settings and useful tips you can read [here](https://templ.guide/commands-and-tools/ide-support).

[↑ Table of contents](#toc)

<a name="#how-to-use-templ-with-gowebly-cli"></a>
## How to use Templ with Gowebly CLI?

The first thing you should do is verify that you are using Gowebly CLI version `v1.5.0` or higher.

Next, start creating the configuration file:

```console
gowebly init
```

The CLI will generate a `.gowebly.yml` file with the following config:

```yaml
backend:
   module_name: project # (string) option can be any name of your Go module (for example, 'github.com/user/project')
   go_framework: default # (string) option can be one of the values: 'fiber', 'echo', 'chi', or 'default'
   template_engine: default # (string) option can be one of the values: 'templ', or 'default'
   port: 5000 # (int) option can be any port that is not taken up on your system
   timeout:
      read: 5 # (int) option can be any number of seconds, 5 is recommended
      write: 10 # (int) option can be any number of seconds, 10 is recommended

frontend:
   package_name: project # (string) option can be any name of your package.json (for example, 'project')
   css_framework: default # (string) option can be one of the values: 'tailwindcss', 'unocss', or 'default'
   runtime_environment: default # (string) option can be one of the values: 'bun', or 'default'
   htmx: latest # (string) option can be any existing version
   hyperscript: latest # (string) option can be any existing version
```

Yes, you're right! ✨ There is a new option `template_engine` in the `backend` block, which can take (so far) two values:

- `default` to create a new project without a template engine;
- `templ` to create a new project using **Templ**;

Now, change the value of this setting to `templ` and start creating a project:

```console
gowebly create
```

And CLI created a new project with the following structure:

```diff
.
├── assets
│   └── styles.css
├── static
│   ├── favicons
│   │   ├── apple-touch-icon.png
│   │   ├── favicon.ico
│   │   ├── favicon.png
│   │   ├── favicon.svg
│   │   ├── manifest-desktop-screenshot.jpeg
│   │   ├── manifest-mobile-screenshot.jpeg
│   │   └── manifest-touch-icon.svg
│   ├── images
│   │   └── logo.svg
│   ├── htmx.min.js
│   ├── hyperscript.min.js
│   ├── manifest.json
│   └── styles.css
├── templates
│   ├── pages
-   │   ├── index.html
+   │   ├── index.templ
+   │   └── index_templ.go
-   ├── main.html
+   ├── main.templ
+   └── main_templ.go
├── go.mod
├── go.sum
├── handlers.go
├── main.go
├── package-lock.json
├── package.json
└── server.go
```

Files with the extension `*.templ` are our working files, and those with `*_templ.go` in the name are automatically generated for us with Templ.

> ❗️ Attention: Do not edit the `*_templ.go` Go files in any way. **They will be generated automatically**. Work only with `*.templ` files.

[↑ Table of contents](#toc)

<a name="#templates-content"></a>
## Templates content

Okay, now let's take a look at the template files.

The `./templates/main.templ` template contains the basic layout for the web application and can be modified as you wish:

```go
// ./templates/main.templ

package templates

import "project/templates/pages"

// Define a secure policy for Content-Security-Policy header.
var metaContentSecurePolicy = "default-src 'self'; style-src 'self' 'unsafe-inline'; script-src 'self' https://unpkg.com 'unsafe-inline' 'unsafe-eval';"

templ Layout(title string, metaTags, bodyContent templ.Component) {
	<!DOCTYPE html>
	<html lang="en">
		<head>
			<meta charset="UTF-8"/>
			<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
			<meta http-equiv="X-UA-Compatible" content="ie=edge"/>
			<meta http-equiv="Content-Security-Policy" content={ metaContentSecurePolicy }/>
			<meta name="theme-color" content="#FEFEF5"/>
			<title>{ title }</title>
			{! metaTags }
			<link rel="manifest" href="/static/manifest.json"/>
			<link rel="apple-touch-icon" href="/static/favicons/apple-touch-icon.png"/>
			<link rel="shortcut icon" href="/static/favicons/favicon.ico" type="image/x-icon"/>
			<link rel="icon" href="/static/favicons/favicon.svg" type="image/svg+xml"/>
			<link rel="icon" href="/static/favicons/favicon.png" sizes="any"/>
			<link href="/static/styles.css" rel="stylesheet"/>
		</head>
		<body onload={ pages.BodyScripts() }>
			{! bodyContent }
			<script src="/static/htmx.min.js"></script>
			<script src="/static/hyperscript.min.js"></script>
		</body>
	</html>
}
```

And the `./templates/pages/index.templ` template will contain the content of the home page (accessible at `/`):

```go
// ./templates/pages/index.templ

package pages

// MetaTags defines meta tags.

templ MetaTags(keywords, description string) {
	<meta name="keywords" content={ keywords }/>
	<meta name="description" content={ description }/>
}

// styledTextStyles defines CSS styles for component.

css styledTextStyles() {
	color: blue;
	margin: 1rem 0;
}

// BodyContent defines HTML content.

templ BodyContent(h1, text string) {
	<main>
		<div>
			<p>
				<img width="224px" src="/static/images/logo.svg" alt="logo"/>
			</p>
			<div>
				<h1>{ h1 }</h1>
				<p>{ text }</p>
				<p class={ styledTextStyles() }>
					Hello from <strong>templ</strong>!
					<br/>
					Edit this styled text in the './templates/pages/index.templ' file.
				</p>
				<p>
					<a href="https://gowebly.org" target="_blank">Documentation</a>,
					<a href="https://github.com/gowebly/gowebly" target="_blank">GitHub</a>
				</p>
			</div>
			<div>
				<p><button hx-get="/api/show" hx-target="#htmx-result">Show content</button></p>
				<div id="htmx-result"></div>
			</div>
		</div>
	</main>
}

// BodyScripts defines JavaScript code.

script BodyScripts() {
	console.log("Hello from templ!", "Edit this JavaScript code in the './templates/pages/index.templ' file.");
}
```

The code in the templates is as **readable** and **easy to understand** as possible for **any** developer who has ever made sites in HTML and understands Go.

[↑ Table of contents](#toc)

<a name="#backend-handler-changes"></a>
## Backend handler changes

If it is clear with the templates themselves, here are the changes that `handlers.go` has undergone:

```go
// handlers.go

// ...

// indexViewHandler handles a view for the index page.
func indexViewHandler(w http.ResponseWriter, r *http.Request) {
	// Check, if the current URL is '/'.
	if r.URL.Path != "/" {
		// If not, return HTTP 404 error.
		http.NotFound(w, r)
		slog.Error("render page", "method", r.Method, "status", http.StatusNotFound, "path", r.URL.Path)
		return
	}

	// Define template functions.
	metaTags := pages.MetaTags(
		"gowebly, htmx example page, go with htmx",               // define meta keywords
		"Welcome to example! You're here because it worked out.", // define meta description
	)
	bodyContent := pages.BodyContent(
		"Welcome to example!",                // define h1 text
		"You're here because it worked out.", // define p text
	)

	// Define template layout.
	if err := templates.Layout(
		"Welcome to example!", // define title text
		metaTags, bodyContent,
	).Render(r.Context(), w); err != nil {
		// Send HTTP 500 error with log.
		w.WriteHeader(http.StatusInternalServerError)
		slog.Error("render template", "method", r.Method, "status", http.StatusInternalServerError, "path", r.URL.Path)
		return
	}

	// Send log message.
	slog.Info("render page", "method", r.Method, "status", http.StatusOK, "path", r.URL.Path)
}

// ...
```

The `templates.Layout()` Go function is the Templ function from the `./templates/main.templ` template, which was kindly generated automatically when the project was created.

> 💡 Note: The [Gowebly CLI](https://github.com/gowebly/gowebly) knows that your project contains a Templ templer and will generate code from the `*.templ` templates every time it changes (hot-reload).

Let's run your project:

```console
gowebly run
```

![gowebly screenshot](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/53xdfzq9gbcke9mspksf.png)

Yeah, it's just working! 🎉

[↑ Table of contents](#toc)

<a name=""></a>
## Photos and videos by

- Vic Shóstak https://github.com/koddr
- Templ authors https://github.com/a-h/templ

<!-- Article links -->

[ah_templ_url]: https://github.com/a-h/templ

<a name=""></a>
## P.S.

If you want more articles (like this) on this blog, then post a comment below and subscribe to me. Thanks! 😻

And of course, you can help me make developers' lives even better! Just connect to one of my projects as a contributor. It's easy!

My projects that need your help (and stars) 👇

- 🔥 [gowebly][gowebly_url]: A next-generation CLI tool for easily build amazing web applications with Go on the backend, using htmx & hyperscript and the most popular atomic/utility-first CSS frameworks on the frontend.
- ✨ [create-go-app][cgapp_url]: Create a new production-ready project with Go backend, frontend and deploy automation by running one CLI command.
- 🏃 [yatr][yatr_url]: Yet Another Task Runner allows you to organize and automate your routine operations that you normally do in Makefile (or else) for each project.
- 📚 [gosl][gosl_url]: The Go Snippet Library provides snippets collection for working with routine operations in your Go programs with a super user-friendly API and the most efficient performance.
- 🏄‍♂️ [csv2api][csv2api_url]: The parser reads the CSV file with the raw data, filters the records, identifies fields to be changed, and sends a request to update the data to the specified endpoint of your REST API.
- 🚴 [json2csv][json2csv_url]: The parser can read given folder with JSON files, filtering and qualifying input data with intent & stop words dictionaries and save results to CSV files by given chunk size.

<!-- Footer links -->

[gowebly_url]: https://github.com/gowebly
[cgapp_url]: https://github.com/create-go-app
[yatr_url]: https://github.com/koddr/yatr
[gosl_url]: https://github.com/koddr/gosl
[csv2api_url]: https://github.com/koddr/csv2api
[json2csv_url]: https://github.com/koddr/json2csv