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

One of The Easiest Ways to Host your Go Web App
5 Sep 2023

One of The Easiest Ways to Host your Go Web App

Hello! In this post, I will explain the cost-effective method I use to host my Go web applications with varying levels of complexity, all starting from as low as $5 per month. This method also allows to easy deploy and scale your golang application.

go hosting digitalocean docker
103 Early Hints in Go, or the new Way of How to Improve Performance of a Web Page written in Go
14 Nov 2022

103 Early Hints in Go, or the new Way of How to Improve Performance of a Web Page written in Go

Since Go 1.19 we can use a new 103 (Early Hints) http status code when we create web applications. Let’s figure out how and when this could help us. We are going to create a simple golang web server that servers some html content. One html page will be served with 103 header and another one without. After loading comparison we will see how early hints can improve page performance.

go golang performance
Example of how Golang generics minimize the amount of code you need to write
9 Jun 2022

Example of how Golang generics minimize the amount of code you need to write

I guess that almost everyone in the go community was exciting when Go 1.18 was released, especially because of generics. Some days ago I decided to try generics in the real-world application, by refactoring some of its pieces, related to a caching logic.

go generics redis cache
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