---
title: "Mastering Flash Messages In PRG Pattern"
description: "Build better UX with Go! Master the Post/Redirect/Get (PRG) pattern and implement safe, read-once Flash messages in Golang. Step-by-step code tutorial."
slug: "mastering-flash-messages-in-prg-pattern"
url: "https://furkanbaytekin.dev/blogs/mastering-flash-messages-in-prg-pattern"
author: "Furkan Baytekin"
category: "Software"
tags: []
published: 2026-10-02T06:25:59Z
updated: 2026-10-02T06:25:59Z
readTime: 3
cover: "https://myblogcms.furkanbaytekin.dev/uploads/cover/45a87723-e6c0-47ac-bdac-532a8c59d063"
---

When building web applications with standard HTML forms, user experience often hinges on two crucial details: **preventing duplicate form submissions** and **providing clear, immediate feedback**.

If you render a response directly from an HTML `POST` request, your users run into a common annoyance: refreshing the page triggers a scary browser warning asking to re-submit form data. Even worse, accidental re-submissions can duplicate database entries or charge a customer twice.

The solution is a classic web architecture pattern known as **Post/Redirect/Get (PRG)** paired with **Flash Messages**. In this guide, we'll explore how the PRG pattern works, why Flash messages are essential, and how to implement a clean, thread-safe Flash message system from scratch in idiomatic **Go (Golang)**.

---

## What is the Post/Redirect/Get (PRG) Pattern?

The **Post/Redirect/Get (PRG)** pattern is a web development design strategy that prevents duplicate form submissions and keeps your browser history clean.

Instead of rendering HTML directly in response to a `POST` request, the server returns an HTTP redirect response (usually `303 See Other` or `302 Found`). The browser then follows this redirect by sending a `GET` request to display the target page.

```
+---------+              +--------+
| Browser |              | Server |
+---------+              +--------+
     |                        |
     |--- 1. POST /login ---->| (Processes data, creates Flash message)
     |                        |
     |<-- 2. 303 Redirect ----| (Redirects to /dashboard)
     |                        |
     |--- 3. GET /dashboard ->| (Reads & clears Flash message)
     |                        |
     |<-- 4. 200 OK (HTML) ---| (Renders page with feedback)
     v                        v

```

### Why PRG is Essential:

* **Prevents Duplicate Submissions:** Hitting `F5` or Refresh only repeats the final `GET` request, not the state-changing `POST`.
* **Bookmarking & Sharing:** Users can safely bookmark or share the resulting page URL without re-triggering server logic.
* **Clean Browser History:** The back button behaves predictably without warning prompts.

---

## The Missing Link: What is a Flash Message?

While the PRG pattern solves the double-submission issue, it introduces a new challenge: **HTTP is stateless**.

When the server redirects the browser from a `POST` route to a `GET` route, any context about the operation (such as *"Account created successfully"* or *"Invalid password"*) is lost.

A **Flash Message** is a temporary notification stored in session memory or an encrypted cookie. It follows a **read-once** lifecycle:

1. **Created** during the `POST` handler execution.
2. **Stored** temporarily (in a cookie or server session).
3. **Retrieved** during the subsequent `GET` request.
4. **Deleted** immediately after being rendered to the user.

---

## Implementing Flash Messages in Go

Let's build a working Go application that implements the PRG pattern using encrypted browser cookies for Flash messages. We'll use the popular `gorilla/sessions` package to handle secure cookie storage.

### Prerequisites

Initialize a new Go module and install the Gorilla Sessions package:

```bash
go mod init go-flash-demo
go get github.com/gorilla/sessions

```

### Step 1: Complete Go Server Implementation

Create a file named `main.go`:

```go
package main

import (
	"html/template"
	"log"
	"net/http"
	"os"

	"github.com/gorilla/sessions"
)

// Initialize the cookie store with a secret key for encryption.
// In production, load this key from a secure environment variable.
var store = sessions.NewCookieStore([]byte(getEnv("SESSION_KEY", "super-secret-key-32-bytes-long!")))

const flashSessionName = "flash-session"

func main() {
	http.HandleFunc("/", handleHome)
	http.HandleFunc("/submit", handleSubmit)
	http.HandleFunc("/dashboard", handleDashboard)

	log.Println("Server starting on http://localhost:8080...")
	if err := http.ListenAndServe(":8080", nil); err != nil {
		log.Fatalf("Server failed: %v", err)
	}
}

// Helper function to set a flash message in the cookie session.
func setFlash(w http.ResponseWriter, r *http.Request, name string, value string) {
	session, _ := store.Get(r, flashSessionName)
	session.AddFlash(value, name)
	if err := session.Save(r, w); err != nil {
		http.Error(w, "Failed to save session", http.StatusInternalServerError)
	}
}

// Helper function to retrieve and clear flash messages.
func getFlash(w http.ResponseWriter, r *http.Request, name string) []string {
	session, _ := store.Get(r, flashSessionName)
	flashes := session.Flashes(name)

	// Saving the session after reading flashes clears them automatically.
	if err := session.Save(r, w); err != nil {
		log.Printf("Failed to clear flash session: %v", err)
	}

	var result []string
	for _, f := range flashes {
		if str, ok := f.(string); ok {
			result = append(result, str)
		}
	}
	return result
}

// 1. GET / - Renders the initial form
func handleHome(w http.ResponseWriter, r *http.Request) {
	if r.URL.Path != "/" {
		http.NotFound(w, r)
		return
	}
	renderTemplate(w, "templates/index.html", nil)
}

// 2. POST /submit - Processes form data and executes Redirect
func handleSubmit(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
		return
	}

	username := r.FormValue("username")

	// Basic validation
	if username == "" {
		setFlash(w, r, "error", "Username cannot be empty!")
		http.Redirect(w, r, "/", http.StatusSeeOther) // Redirect back to home
		return
	}

	// Business logic execution (e.g., database save)
	log.Printf("Successfully registered user: %s", username)

	// Set success notification and redirect using PRG pattern
	setFlash(w, r, "success", "Welcome aboard, "+username+"! Your account is active.")
	http.Redirect(w, r, "/dashboard", http.StatusSeeOther)
}

// 3. GET /dashboard - Renders page with flash messages
func handleDashboard(w http.ResponseWriter, r *http.Request) {
	successMessages := getFlash(w, r, "success")

	data := map[string]interface{}{
		"Successes": successMessages,
	}

	renderTemplate(w, "templates/dashboard.html", data)
}

func renderTemplate(w http.ResponseWriter, tmplFile string, data interface{}) {
	tmpl, err := template.ParseFiles(tmplFile)
	if err != nil {
		http.Error(w, "Template parsing error: "+err.Error(), http.StatusInternalServerError)
		return
	}
	if err := tmpl.Execute(w, tmpl); err != nil {
		http.Error(w, "Template execution error: "+err.Error(), http.StatusInternalServerError)
	}
}

func getEnv(key, fallback string) string {
	if value, ok := os.LookupEnv(key); ok {
		return value
	}
	return fallback
}

```

---

### Step 2: Creating the HTML Templates

Create a folder named `templates` and add the following two template files:

#### `templates/index.html`

```html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>PRG Pattern in Go</title>
    <style>
        body { font-family: sans-serif; max-width: 500px; margin: 50px auto; padding: 20px; }
        .alert-error { background-color: #f8d7da; color: #721c24; padding: 10px; margin-bottom: 15px; border-radius: 4px; }
        .form-group { margin-bottom: 15px; }
        input[type="text"] { width: 100%; padding: 8px; box-sizing: border-box; }
        button { padding: 10px 15px; background-color: #007bff; color: white; border: none; border-radius: 4px; cursor: pointer; }
    </style>
</head>
<body>
    <h2>User Registration</h2>

    <form action="/submit" method="POST">
        <div class="form-group">
            <label for="username">Username:</label>
            <input type="text" id="username" name="username" placeholder="Enter name">
        </div>
        <button type="submit">Register</button>
    </form>
</body>
</html>

```

#### `templates/dashboard.html`

```html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Dashboard</title>
    <style>
        body { font-family: sans-serif; max-width: 500px; margin: 50px auto; padding: 20px; }
        .alert-success { background-color: #d4edda; color: #155724; padding: 10px; margin-bottom: 15px; border-radius: 4px; }
    </style>
</head>
<body>
    {{range .Successes}}
        <div class="alert-success">{{.}}</div>
    {{end}}

    <h1>Dashboard</h1>
    <p>Welcome to your application dashboard!</p>
    <a href="/">Back to Form</a>
</body>
</html>

```

---

## Key Best Practices for Flash Messages in Go

1. **Use HTTP `303 See Other` for Redirects:**
When redirecting after a `POST` request, prefer `http.StatusSeeOther` (`303`) over `http.StatusFound` (`302`). HTTP 303 explicitly guarantees that the browser will issue a `GET` request to the target URL regardless of the original method.
2. **Secure Your Cookie Key:**
Flash cookies must be signed (and ideally encrypted) to prevent client-side tampering. Never hardcode session keys in production source code; always pull them from environment variables or secret vaults.
3. **Keep Messages Small:**
Cookies have a browser limit of approximately **4KB**. Store short string messages or status codes in flash notifications rather than large payload objects.
4. **Category-Based Flashes:**
Organize flash messages by types (e.g., `"success"`, `"error"`, `"warning"`). Gorilla's `session.AddFlash(value, key)` lets you scope messages logically for styled rendering in HTML templates.

---

## Conclusion

Combining the **Post/Redirect/Get (PRG)** pattern with **Flash Messages** provides a robust foundation for handling web forms. It prevents accidental double-submits, protects server state integrity, and provides users with immediate, intuitive visual feedback.

With Go's standard library `http` package and lightweight session managers like `gorilla/sessions`, implementing clean and secure Flash messages takes only a few lines of idiomatic code.
