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:
When a password is correct, we will be redirected to the /admin
path with the 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.
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 dockerSince 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.
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 cacheThis 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 raceRecently, 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