How to Show Flash Messages in Go web applications (with Echo framework)
4 Feb 2021

How to Show Flash Messages in Go web applications (with Echo framework)

When we create a web application, usually, there a need to communicate with the users to inform them about the results of their actions. The easiest way to communicate - is to send messages. These messages might be warnings, errors, or just informational text. In this article, we will improve the UX of our user authentication application from the previous article by adding an error flash message when the user entered a wrong password and a success message after user authorisation.

We are going to use cookies to store messages in-between requests. To not reinvent the wheel, we will install Gorilla Sessions package.

go get github.com/gorilla/sessions

Next, let’s create a messages package (messages/messages.go):

package messages

import (
	"github.com/gorilla/sessions"
	"github.com/labstack/echo/v4"
)

// Name of the cookie.
const sessionName = "fmessages"

func getCookieStore() *sessions.CookieStore {
	// In real-world applications, use env variables to store the session key.
	sessionKey := "test-session-key"
	return sessions.NewCookieStore([]byte(sessionKey))
}

// Set adds a new message into the cookie storage.
func Set(c echo.Context, name, value string) {
	session, _ := getCookieStore().Get(c.Request(), sessionName)
	session.AddFlash(value, name)

	session.Save(c.Request(), c.Response())
}

// Get gets flash messages from the cookie storage.
func Get(c echo.Context, name string) []string {
	session, _ := getCookieStore().Get(c.Request(), sessionName)
	fm := session.Flashes(name)
	// If we have some messages.
	if len(fm) > 0 {
		session.Save(c.Request(), c.Response())
		// Initiate a strings slice to return messages.
		var flashes []string
		for _, fl := range fm {
			// Add message to the slice.
			flashes = append(flashes, fl.(string))
		}

		return flashes
	}
	return nil
}

We now have a possibility to easily Set and Get flash messages. Let’s integrate it, first, to the sign-in form, which we created in the previous article.

I’m going to modify controllers/signin.go file. Inside SignIn() function we can replace

return echo.NewHTTPError(http.StatusUnauthorized, "Password is incorrect")

with

messages.Set(c, "error", "Password is incorrect!")
return c.Redirect(http.StatusMovedPermanently, c.Echo().Reverse("userSignInForm"))

And, if the password is correct, we going to set a message also:

messages.Set(c, "message", "Password is correct, you have been authenticated!")

The final SignIn() function will look like this:

// SignIn will be executed after SignInForm submission.
func SignIn() echo.HandlerFunc {
	return func(c echo.Context) error {
		// Load our "test" user.
		storedUser := user.LoadTestUser()
		// Initiate a new User struct.
		u := new(user.User)
		// Parse the submitted data and fill the User struct with the data from the SignIn form.
		if err := c.Bind(u); err != nil {
			return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
		}
		// Compare the stored hashed password, with the hashed version of the password that was received
		if err := bcrypt.CompareHashAndPassword([]byte(storedUser.Password), []byte(u.Password)); err != nil {
			// If the two passwords don't match, set a message and reload the page.
            messages.Set(c, "error", "Password is incorrect!")
			return c.Redirect(http.StatusMovedPermanently, c.Echo().Reverse("userSignInForm"))
		}
		// If password is correct, generate tokens and set cookies.
		err := auth.GenerateTokensAndSetCookies(storedUser, c)

		if err != nil {
			return echo.NewHTTPError(http.StatusUnauthorized, "Token is incorrect")
		}
		messages.Set(c, "message", "Password is correct, you have been authenticated!")
		return c.Redirect(http.StatusMovedPermanently, "/admin")
	}
}

Once we set messages, we need to display them.

First, I will add message displaying logic to the SignIn form. We need to modify SignInForm() function inside controllers/signin.go, by adding additional data with messages to the template execution function:

tmpl, err := template.ParseFiles(path.Join("templates", "signIn.html"), path.Join("templates", "messages.html"))
data := make(map[string]interface{})
data["errors"] = messages.Get(c, "error")
err := tmpl.Execute(c.Response().Writer, data);

The final SignInForm() function will look like this:

// SignInForm responsible for signIn Form rendering.
func SignInForm() echo.HandlerFunc {
	return func(c echo.Context) error {
		tmpl, err := template.ParseFiles(path.Join("templates", "signIn.html"), path.Join("templates", "messages.html"))
		data := make(map[string]interface{})
		data["errors"] = messages.Get(c, "error")
		if err != nil {
			return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
		}
		if err := tmpl.Execute(c.Response().Writer, data); err != nil {
			return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
		}

		return nil
	}
}

As you probably noticed, we introduced messages.html to the template.ParseFiles() function. There we will control how to visualize messages. Let’s add this template inside templates folder:

{{ define "messages" }}
  {{ if index . "errors" }}
    <div class="errors" style="background: darksalmon; padding: 10px;">
        {{ range index . "errors" }}
        <p>{{ . }}</p>
        {{ end }}
    </div>
  {{ end }}
  {{ if index . "messages" }}
    <div class="messages" style="background: darkseagreen; padding: 10px;">
        {{ range index . "messages" }}
        <p>{{ . }}</p>
        {{ end }}
    </div>
  {{ end }}

{{ end }}

Next, we need to add the reference to the messages template inside signIn.html and admin.html:

{{ template "messages" . }}

Now, we can try how does it work.

Let’s run the server go run main.go and go to the /user/signin path. When we submit the form with the wrong password, we should see this: Error Message

When a password is correct, we will be redirected to the /admin path with the message: Success Message

In this example, I was using the Echo framework, but it will be super easy to modify the code for your specific use-case.

The complete source code you can find here.

Related posts

Concurrent Map Writing and Reading in Go, or how to deal with the data races.
16 Jul 2021

Concurrent Map Writing and Reading in Go, or how to deal with the data races.

This time, I will show you how to work with the maps in go effectively and prevent the occurrence of the data race errors. Data races happen when several goroutines access the same resource concurrently and at least one of the accesses is a write.

go concurrent map data race
Ristretto - the Most Performant Concurrent Cache Library for Go
2 Mar 2021

Ristretto - the Most Performant Concurrent Cache Library for Go

Recently, I discovered a surprisingly reliable memory caching solution, which I’m planning to use in all my further applications to increase performance. In this blog post, I will share some code examples of how you can integrate Ristretto caching library into your application.

go caching ristretto performance
User Authentication in Go Echo with JWT
28 Jan 2021

User Authentication in Go Echo with JWT

In this article, we will build a simple user authentication functionality using JWT (JSON Web Token). In the examples, I’m going to use a Go Echo framework. This will allow us to avoid writing some boilerplate code.

go authentication JWT Echo
A Simple Queue Implementation in Golang with channels
12 Jan 2021

A Simple Queue Implementation in Golang with channels

In this post, I’m going to show the way how we can implement a simple queue in Golang, using channels.

go queue channel goroutine
How to Sort Strings With Go Alphabetically in Any Language
4 Jan 2021

How to Sort Strings With Go Alphabetically in Any Language

In this article I’m going to show how easy we can sort strings alphabetically in different languages, using Go. It seems like an easy task if we want to sort English words, however, it’s not so trivial if we want to sort correctly strings with special characters or in other languages, i.e Cyrillic based.

go sorting alphabetical sort