Templates



Templates are the implementation for your generator. They are parameterized files which are filled in with data from the schema and user input.

Generators have several kinds of files that end up in the output

  1. Once Templates - used to generate a single file, like main.go or index.js
  2. Repeated Templates - generate a file for each element, like routes in this example
  3. Partial Templates - reusable template snippets which are available in all full templates
  4. Static Files - copied directly into the output, bypassing the template engine
  5. Config Files - generate yaml or json into the output, bypassing the template engine

Templates are based on Go text/template with extra helpers and conventions. We will cover the basics in the the-walkthrough and they should be familiar to other text templating systems. Read [template writing(/code-generation/template-writing/) to learn more about the details.

Once Templates

These files are needed once for every server we generate. Some have minimal templating and others loop over values, like router.go.

templates/go.mod

module {{ .SERVER.GoModule }}

go 1.20

require (
	github.com/labstack/echo-contrib v0.15.0
	github.com/labstack/echo/v4 v4.11.1
)

templates/server.go

package main

import (
	"context"
	"os"
	"os/signal"
	"time"

	"github.com/labstack/echo/v4"
)

var PORT string = ":8080"

func main() {
	// create echo server
	e := echo.New()
	e.HideBanner = true

	// add middleware
	err := setupMiddleware(e)
	if err != nil {
		panic(err)
	}

	// setup router
	err = setupRouter(e)
	if err != nil {
		panic(err)
	}

	//
	// code to run server and enable graceful shutdown
	//
	// Start server with background goroutine
	go func() {
		if err := e.Start(PORT); err != nil {
			e.Logger.Info("shutting down the server")
		}
	}()

	// Wait for interrupt signal to gracefully shutdown the server with
	// a timeout of 10 seconds.
	quit := make(chan os.Signal)
	signal.Notify(quit, os.Interrupt)

	// wait on a quit signal
	<-quit

	// start the shutdown process
	ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
	defer cancel()
	if err := e.Shutdown(ctx); err != nil {
		e.Logger.Fatal(err)
	}
}

templates/router.go

package main

import (
	"net/http"

	"github.com/labstack/echo/v4"
	"github.com/labstack/echo-contrib/echoprometheus"

	{{ if gt (len .SERVER.Routes ) 1 }}
	"{{ .SERVER.GoModule }}/routes"
	{{ end }}
)

func setupRouter(e *echo.Echo) error {

	// Internal routes
	e.GET("/internal/alive", func(c echo.Context) error {
		return c.NoContent(http.StatusOK)
	})

	{{ if .SERVER.Prometheus }}
	e.GET("/internal/metrics", echoprometheus.NewHandler())
	{{ end }}

	// Application routes group
	g := e.Group("")

	// Register the routes
	{{ range $R := .SERVER.Routes -}}
	routes.{{ $R.Name }}Routes(g)
	{{ end }}

	return nil
}

templates/middleware.go

package main

import (
	"github.com/labstack/echo/v4"
	"github.com/labstack/echo/v4/middleware"
	"github.com/labstack/echo-contrib/echoprometheus"
)

func setupMiddleware(e *echo.Echo) error {
	// ensure request IDs
	e.Use(middleware.RequestID())

	// setup logging middleware
	e.Use(middleware.Logger())

	// setup recovery middleware
	e.Use(middleware.Recover())

	{{ if .SERVER.Auth }}
	// setup auth middleware
	setupAuth(e)
	{{ end }}

	{{ if .SERVER.Prometheus }}
	// setup metrics middleware
	e.Use(echoprometheus.NewMiddleware("{{ .SERVER.Name }}"))
	{{ end }}

	return nil
}

Repeated and Partial Templates

We separate the handler into a template which uses the partial. This is for demonstration purpose here and will be more useful in the “full-example” section where the implementation is more complete.

templates/route.go

package routes

import (
	"net/http"

	"github.com/labstack/echo/v4"
)

// {{ .ROUTE.Name }}Routes sets up the routes in a router Group
func {{ .ROUTE.Name }}Routes(G *echo.Group) {
	g := G.Group("{{ .ROUTE.Path }}{{ range $PATH := .ROUTE.Params }}/:{{$PATH}}{{ end }}")
	g.{{.ROUTE.Method}}( "", {{.ROUTE.Name}}Handler)

	// we'll handle sub-routes here in "full-example"
}

// Handler implementation is in a partial template
{{ template "handler.go" .ROUTE }}

partials/handler.go

{{ $ROUTE := . }}
func {{ $ROUTE.Name }}Handler(c echo.Context) (err error) {

	// process any path and query params
	{{ range $P := $ROUTE.Params }}
	{{ $P }} := c.Param("{{ $P }}")
	{{ end }}

	{{ range $Q := $ROUTE.Query }}
	{{ $Q }} := c.QueryParam("{{ $Q }}")
	{{ end }}

	{{ if $ROUTE.Body }}
	// custom body
	{{ $ROUTE.Body }}
	{{ else }}
	// default body
	c.String(http.StatusNotImplemented, "Not Implemented")
	{{ end }}

	return nil
}

Static Files

By default, anything in the static/ dir will be copied into the output dir. You can configure one or more manually via the generator by setting the Statics field on your generator.

Config Files

Rendered Output Files

Here we can see the result of code generation for a sample of the files. We will actually generate these in the next section. They are provided here so you can see the input / output pairs on a single page.

output/middleware.go

package main

import (
	"github.com/labstack/echo-contrib/echoprometheus"
	"github.com/labstack/echo/v4"
	"github.com/labstack/echo/v4/middleware"
)

func setupMiddleware(e *echo.Echo) error {
	// ensure request IDs
	e.Use(middleware.RequestID())

	// setup logging middleware
	e.Use(middleware.Logger())

	// setup recovery middleware
	e.Use(middleware.Recover())

	// setup auth middleware
	setupAuth(e)

	// setup metrics middleware
	e.Use(echoprometheus.NewMiddleware("Example"))

	return nil
}

output/router.go

package main

import (
	"net/http"

	"github.com/labstack/echo-contrib/echoprometheus"
	"github.com/labstack/echo/v4"

	"hof.io/docs/example/routes"
)

func setupRouter(e *echo.Echo) error {

	// Internal routes
	e.GET("/internal/alive", func(c echo.Context) error {
		return c.NoContent(http.StatusOK)
	})

	e.GET("/internal/metrics", echoprometheus.NewHandler())

	// Application routes group
	g := e.Group("")

	// Register the routes
	routes.EchoQRoutes(g)
	routes.EchoPRoutes(g)
	routes.HelloRoutes(g)

	return nil
}

output/routes/Hello.go

package routes

import (
	"net/http"

	"github.com/labstack/echo/v4"
)

// HelloRoutes sets up the routes in a router Group
func HelloRoutes(G *echo.Group) {
	g := G.Group("/hello")
	g.GET("", HelloHandler)

	// we'll handle sub-routes here in "full-example"
}

// Handler implementation is in a partial template

func HelloHandler(c echo.Context) (err error) {

	// process any path and query params

	msg := c.QueryParam("msg")

	// custom body
	if msg == "" {
		msg = "hello world"
	}
	c.String(http.StatusOK, msg)

	return nil
}