Zainokta
  • Home
  • About
  • Experience
  • Projects
  • Blog
© 2025 Zainokta. All Rights Reserved.
Back to Blog

Creating a Website Using Golang + Templ

February 7, 20255 min read
golangtempl

image

Back then, people got used to creating a website using HTML, CSS, and Javascript only with PHP for the backend. Currently, plenty of JavaScript frameworks exist, such as React.js, Next.js, Vue.js, Nuxt, Svelte, Sveltekit, Astro, and many more.

In this article, I'm trying to create my website using Golang, HTML, CSS, and some JavaScript. I will be using Templ for the front-end HTML templating. When you are coming from Ruby on Rails or Laravel, you are already familiar with HTML templating, Rails has ERB, and Laravel has Blade. The purpose is the same, to integrate the backend code with the HTML directly, and also create a modular HTML module, so you can re-use it somewhere in the project.

    go install github.com/a-h/templ/cmd/templ@latest

I will use the latest version of the Templ (as this article is written, the version is v0.2.778), and yes, it's not version v1 yet. Anyway, after installing the Templ binary in the machine, I started to create the empty Golang project, let's call it personal.

    mkdir personal && cd personal
    touch main.go
    go mod init personal

Yay, the project is now initiated, then in the main.go file, I create a simple HTTP server, in this article, I will use Echo.

    package main

    import (
        "context"
        "fmt"
        "log"
        "net/http"
        "os"
        "os/signal"
        "personal/internal/config"
        "syscall"

        "github.com/caarlos0/env/v11"
        "github.com/labstack/echo/v4"
        "golang.org/x/net/http2"
        "golang.org/x/net/http2/h2c"
    )

    func main() {
        cfg, err := env.ParseAsconfig.Config
        if err != nil {
            log.Println("error loading config")
        }

        e := echo.New()

        h2s := &http2.Server{
            MaxConcurrentStreams: 250,
            MaxReadFrameSize:     1048576,
            IdleTimeout:          cfg.GracefulTimeout,
        }

        s := http.Server{
            Addr:    fmt.Sprintf(":%s", cfg.Port),
            Handler: h2c.NewHandler(e, h2s),
        }

        log.Printf("server running on port: %s", cfg.Port)

        go func() {
            if err := s.ListenAndServe(); err != nil && err != http.ErrServerClosed {
                log.Println("shutting down the server")
            }
        }()

        quit := make(chan os.Signal, 1)
        signal.Notify(quit, os.Interrupt, syscall.SIGTERM)
        <-quit

        log.Println("shutting down server...")

        ctx, cancel := context.WithTimeout(context.Background(), cfg.GracefulTimeout)
        defer cancel()

        if err := s.Shutdown(ctx); err != nil {
            log.Println("server forced to shutdown")
        }

        log.Println("server exiting")
    }

Run go mod tidy to install all of the dependencies. After that, create a new file named config.go to hold the configuration of the server.

    package config

    import "time"

    type Config struct {
        Port            string        `env:"PORT" envDefault:"8080"`
        Env             string        `env:"ENV" envDefault:"dev"`
        GracefulTimeout time.Duration `env:"GRACEFUL_TIMEOUT" envDefault:"10s"`
    }

To make sure the server is running, run go run main.go in the terminal. The output should be like this:

image

After the server can be running in the terminal, now is the time to integrate the Templ into our project. First, create a directory named views inside internal, the project path should be like this: /personal/internal/views. Then, create a new file named layout.templ for the base layout of the project.

    package views

    templ head(title string, description string) {
        {title}
    }

    templ Layout(title string, description string, content templ.Component) {
      @head(title, description)
      @content
    }

A quick explanation for the code above is, that I created a new HTML layout for the project with a function named Layout, it has 3 parameters, which are title, description, and content. There is another function called head to store the website metadata. As you can see, the function is not used func like normal Golang, but it's used templ for the function initialization, and it works just like the Golang function.

Note, there is a VSCode extension for the Templ so you can navigate through the functions more easily.

Now I already have my Layout for the base layout of the project. The next step is generating the layout so I can call the templ function in the main.go. To generate the Golang file using the Templ, just run the templ generate command.

You might see a warning in your terminal saying that templ version check: templ not found in go.mod file, don't panic, just add the templ in your go.mod file by running go get github.com/a-h/templ. After you add it to the go.mod the warning should be gone when you re-generate it. Under the /personal/internal/views/layout.templ, you will see a new file named layout_templ.go which is the file that Templ has generated, so the Golang code can call the Templ function (in this case the Templ function created above named Layout) directly.

The next step is creating the home page for the project website. In this section, I create a new file inside /personal/internal/views with the name home.templ, and then run the templ generate command to generate the templ golang files.

    package views

    templ Home() {
      Hello World.
    }

I create the HTML as simply as possible so you can easily integrate the code and customize your own later.

Now move back to the main.go file. Currently, we already have 2 generated templ files, the layout_templ.goand home_templ.go. To render it in the Echo handler function, I created a new function to render the templ.Component as HTML. I saved the function inside personal/pkg/renderer directory.

    package renderer

    import (
        "github.com/a-h/templ"
        "github.com/labstack/echo/v4"
    )

    func Render(c echo.Context, statusCode int, t templ.Component) error {
        buf := templ.GetBuffer()
        defer templ.ReleaseBuffer(buf)

        if err := t.Render(c.Request().Context(), buf); err != nil {
            return err
        }

        return c.HTML(statusCode, buf.String())
    }

The function templ.Render will render the template that has been created before into a bytes buffer, and then by using c.HTML we can render the buffer by getting the string content.

Lastly, modify the main.go file by creating a new router, and also we need to import the renderer package so we can render the template.

    package main

    import (
          ...
        "personal/pkg/renderer"
    )
    func main() {
    ...
    e.GET("/", func(c echo.Context) error {
            return renderer.Render(c, http.StatusOK,
                views.Layout("Home",
                "Zainokta's home page, feels like home",
                views.Home()))
        })
    ...
    }

When the server is started by using go run main.go, go to http://localhost:8080/ to see the website, and now it should show Hello World as the content, and you can see in the inspect element tab, that the Hello World content is wrapped inside the main body of the HTML.