# Using Scriggo Templates with the Go Fiber web framework

[Fiber](https://docs.gofiber.io/) is an express-inspired framework for Go. It uses fasthttp under the hood and has support for many features like middleware, JSON, and template engines among others.

The Fiber project has support for a lot of [template engines](https://docs.gofiber.io/guide/templates) but they don't have one for [Scriggo](https://scriggo.com/templates). I like Scriggo because it looks a bit like Jinja (Python), Twig (PHP) and Pebble (Java) template engines which I have used extensively recently.

Fortunately, Fiber allows you to implement your own Template Engine without much hassle. In this article I will share a simple implementation of a simple Scriggo template engine that you can use with Fiber.

## A Simple Server side rendered site in Go

We are going to create a simple server side rendered site with Go using the Scriggo engine. So first of all create a new project, e.g.:

```sh
$ mkdir example-site

$ cd example-site

$ go mod init example.com/site
```

Copy the code for the engine (find it at the bottom of this article) into a file named `scriggo_engine/scriggo_engine.go` so you can import the Engine.

Now, place the following server code in a file named `main.go` in your module directory:

```go
package main

import (
	"log"

	"github.com/gofiber/fiber/v2"
	"example.com/site/scriggo_engine"
)

type Server struct {
	app *fiber.App
}

func (s *Server) NotImplemented(ctx *fiber.Ctx) error {
	return nil
}

func NewServer() *Server {
	engine := scriggo_engine.New("templates", ".html")
	s := &Server{
		app: fiber.New(fiber.Config{
			Views: engine,
		}),
	}

	s.app.Get("/", s.indexPage)

	return s
}

type Contact struct {
	Name   string
	Phone  string
	Email    string
}

func (s *Server) indexPage(ctx *fiber.Ctx) error {
	contacts := []Contact{
		{Name: "John Phiri", Email: "john@example.com", Phone: "(+265) 999 123 456"},
		{Name: "Mary Phiri", Email: "mary@example.com", Phone: "(+265) 999 123 456"},
		{Name: "Jane Phiri", Email: "jane@example.com", Phone: "(+265) 999 123 456"},
	}
	return ctx.Render("index", fiber.Map{
		"contacts": &contacts,
	})
}

func (s *Server) Start(bind string) error {
	return s.app.Listen(bind)
}

func main() {
	server := NewServer()
	err := server.Start("localhost:3000")
	if err != nil {
		log.Fatalf("Failed to run, got error %v", err)
	}
}
```

In a file named `templates/base.html`

```html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{{ Title() }} - My Website</title>
</head>
<body>
    <nav>
        <h1>My Website</h1>

        <ul>
            <li><a href="/home">Home</a></li>
            <li><a href="/about">About</a></li>
            <li><a href="/contact">Contact</a></li>
        </ul>
    </nav>
    {{ Content() }}
    {{ Footer() }}
</body>
</html>
```

In a file named `templates/index.html`

```html
{% extends "base.html" %}
{% macro Title %}Home{% end %}
{% macro Content %}
<section id="app-content">
    <div class="contacts">
        {% for contact in contacts %}
        <li><a href="/contact/{{ contact.Name }}">{{ contact.Phone}}</a> ({{ contact.Email }})</li>
        {% end %}
    </div>
</section>
{% end %}
{% macro Footer %}
 <footer>(c) My Website</footer>
{% end %}
```

## Project Structure

Your directory should look something like this
```
example-site
├───main.go
├───go.mod
├───go.sum
├───scriggo_engine
|    └───scriggo_engine.go
└───templates
    ├───base.html
    └───index.html
```

## Running the code

First we are going to make sure go modules are tidied and downloaded via `go mod tidy` and then we can run the main.go

```sh
$ go mod tidy

$ go run main.go
```

Now go to `http://localhost:3000` you should see a server-side rendered HTML page!  

## Registering Scriggo in-built functions

Typically in template engines we may want to call some functions on our data. Scriggo comes with a lot of built-in functions and using them is relatively straight-foward.

Register the function via `addFunc`

```go
import "github.com/open2b/scriggo/builtin"

// ..
engine.AddFunc("base64", builtin.Base64 )
// ..
```

Use the function in your templates
```html
<div>{{ base64("Hello") }}</div>
```


## Scriggo Template Engine for Fiber

Here is the implementation of the engine code, it has not been battle-tested so if there are bugs or ways to improve it, feel free to reach out on Twitter.

```go
package scriggo_engine

import (
	"fmt"
	"io"
	"net/http"
	"os"
	"path/filepath"
	"strings"
	"sync"

	"github.com/gofiber/fiber/v2"
	"github.com/gofiber/template/utils"
	"github.com/open2b/scriggo"
	"github.com/open2b/scriggo/native"
)

// Engine struct
type Engine struct {
	// views folder
	directory string
	// http.FileSystem supports embedded files
	fileSystem http.FileSystem
	// views extension
	extension string
	// layout variable name that incapsulates the template
	layout string
	// determines if the engine parsed all templates
	loaded bool
	// reload on each render
	reload bool
	// debug prints the parsed templates
	debug bool
	// lock for funcmap and templates
	mutex sync.RWMutex
	// template funcmap
	funcmap native.Declarations
	// templates
	templates map[string]string
	// scriggo filesystem
	fsys scriggo.Files
}

// New returns a Scriggo render engine for Fiber
func New(directory, extension string) *Engine {
	engine := &Engine{
		directory: directory,
		extension: extension,
		layout:    "embed",
		funcmap:   make(native.Declarations),
	}
	engine.AddFunc(engine.layout, func() error {
		return fmt.Errorf("layout called unexpectedly.")
	})
	return engine
}

func NewFileSystem(fs http.FileSystem, extension string) *Engine {
	engine := &Engine{
		directory:  "/",
		fileSystem: fs,
		extension:  extension,
		layout:     "embed",
		funcmap:    make(native.Declarations),
	}
	engine.AddFunc(engine.layout, func() error {
		return fmt.Errorf("layout called unexpectedly.")
	})
	return engine
}

// Layout defines the variable name that will incapsulate the template
func (e *Engine) Layout(key string) *Engine {
	e.layout = key
	return e
}

// Delims sets the action delimiters to the specified strings, to be used in
// templates. An empty delimiter stands for the
// corresponding default: {{ or }}.
func (e *Engine) Delims(left, right string) *Engine {
	fmt.Println("delims: this method is not supported for scriggo")
	return e
}

// AddFunc adds the function to the template's function map.
// It is legal to overwrite elements of the default actions
func (e *Engine) AddFunc(name string, fn native.Declaration) *Engine {
	e.mutex.Lock()
	e.funcmap[name] = fn
	e.mutex.Unlock()
	return e
}

// Reload if set to true the templates are reloading on each render,
// use it when you're in development and you don't want to restart
// the application when you edit a template file.
func (e *Engine) Reload(enabled bool) *Engine {
	e.reload = enabled
	return e
}

// Debug will print the parsed templates when Load is triggered.
func (e *Engine) Debug(enabled bool) *Engine {
	e.debug = enabled
	return e
}

// Load parses the templates to the engine.
func (e *Engine) Load() error {
	// race safe
	e.mutex.Lock()
	defer e.mutex.Unlock()

	e.templates = make(map[string]string)
	e.fsys = scriggo.Files{}

	// Loop trough each directory and register template files
	walkFn := func(path string, info os.FileInfo, err error) error {
		// Return error if exist
		if err != nil {
			return err
		}
		// Skip file if it's a directory or has no file info
		if info == nil || info.IsDir() {
			return nil
		}
		// Skip file if it does not equal the given template extension
		if len(e.extension) >= len(path) || path[len(path)-len(e.extension):] != e.extension {
			return nil
		}
		// Get the relative file path
		// ./views/html/index.tmpl -> index.tmpl
		rel, err := filepath.Rel(e.directory, path)
		if err != nil {
			return err
		}
		// Reverse slashes '\' -> '/' and
		// partials\footer.tmpl -> partials/footer.tmpl
		name := filepath.ToSlash(rel)
		// Remove ext from name 'index.tmpl' -> 'index'
		name = strings.TrimSuffix(name, e.extension)
		// name = strings.Replace(name, e.extension, "", -1)
		// Read the file
		// #gosec G304
		buf, err := utils.ReadFile(path, e.fileSystem)
		if err != nil {
			return err
		}

		e.fsys[filepath.ToSlash(rel)] = buf
		e.templates[name] = filepath.ToSlash(rel)

		// Debugging
		if e.debug {
			fmt.Printf("views: parsed template: %s\n", name)
		}
		return err
	}
	// notify engine that we parsed all templates
	e.loaded = true
	if e.fileSystem != nil {
		return utils.Walk(e.fileSystem, e.directory, walkFn)
	}
	return filepath.Walk(e.directory, walkFn)
}

// Render will execute the template name along with the given values.
func (e *Engine) Render(out io.Writer, template string, binding interface{}, layout ...string) error {
	if !e.loaded || e.reload {
		if e.reload {
			e.loaded = false
		}
		if err := e.Load(); err != nil {
			return err
		}
	}
	templatePath, ok := e.templates[template]
	if !ok {
		return fmt.Errorf("render: template %s does not exist", template)
	}

	opts := &scriggo.BuildOptions{
		Globals: e.funcmap,
	}

	// Register the variables as Globals for Scriggo's type checking to work
	for key, value := range binding.(fiber.Map) {
		opts.Globals[key] = value
	}

	// Build the template.
	tmpl, err := scriggo.BuildTemplate(e.fsys, templatePath, opts)
	if err != nil {
		return err
	}

	return tmpl.Run(out, nil, nil)
}
```


## What's next?

1. Support for embedded filesytems (`embed.FS`) - I suspect it's already possible but the API doesn't look good for doing so yet.

2. Releasing to the public - I am thinking of either releasing this as a module that others can consume or  sending a Pull Request to the Fiber team if they would be interested, or I may just make it a Gist. We will see. Right now the proof of concept works well enough for me to play with on toy projects.

I may update this article later on
