Skip to content

Messages

Flash message support for post-redirect-get feedback.

Package: github.com/oliverandrich/burrow/contrib/messages

Depends on: session

Setup

Register the messages app after session (it depends on session for storage):

srv := burrow.NewServer(
    session.New(),
    csrf.New(),
    messages.New(),
    // ... other apps
)

The messages app installs middleware that reads flash messages from the session into the request context and clears them, giving each message a single-request lifetime.

Adding Messages

Use the convenience helpers inside any handler — typically just before a redirect:

import "github.com/oliverandrich/burrow/contrib/messages"

func (a *App) Create(w http.ResponseWriter, r *http.Request) error {
    // ... create resource ...

    if err := messages.AddSuccess(w, r, "Note created."); err != nil {
        return err
    }
    http.Redirect(w, r, "/notes", http.StatusSeeOther)
    return nil
}

Available helpers:

Helper Level
messages.AddInfo(w, r, text) info
messages.AddSuccess(w, r, text) success
messages.AddWarning(w, r, text) warning
messages.AddError(w, r, text) error

For full control, use messages.Add(w, r, level, text) with any messages.Level.

Reading Messages

Each Message has two fields:

  • Level — one of messages.Info, messages.Success, messages.Warning, messages.Error
  • Text — the message string

In Go Code

msgs := messages.Get(r.Context())
for _, msg := range msgs {
    fmt.Printf("%s: %s\n", msg.Level, msg.Text)
}

In Layout Templates

Messages are available in layout templates via the messages template function (provided by the messages contrib app via HasRequestFuncMap). The four level values (info, success, warning, error) map straight to whatever CSS conventions your layout uses. The example/notes layout uses Tailwind utility classes via a per-level helper define:

{{ range messages -}}
<div class="rounded-md border px-3 py-2 text-sm {{ template "app/alert_classes" .Level }}" role="alert">
    {{ .Text }}
</div>
{{ end -}}

See example/notes/internal/app/templates/app/layout.html for the full app/alert_classes define (one Tailwind class set per level).

Custom Rendering

If you use a different CSS framework, call messages.Get(ctx) directly and map levels to your own classes:

func toastClass(level messages.Level) string {
    switch level {
    case messages.Success: return "toast-success"
    case messages.Warning: return "toast-warning"
    case messages.Error:   return "toast-error"
    default:               return "toast-info"
    }
}

How It Works

The middleware creates a mutable, request-scoped store. Add() writes to both the store and the session cookie. Get() reads from the store and clears the session cookie to prevent double-display.

Redirect flow (post-redirect-get)

  1. Handler calls messages.Add(w, r, level, text) — writes to store + session cookie
  2. Redirect sends the browser to the target page (cookie included)
  3. Middleware on the next request seeds a new store from the session and clears the session
  4. Template calls messages.Get(ctx) — reads from the store, message appears exactly once

Same-request flow (HTMX partial)

  1. Handler calls messages.Add(w, r, level, text) — writes to store + session cookie
  2. Template calls messages.Get(ctx) — reads from the store, clears the session cookie
  3. Browser receives the response with the cleared cookie — no message persists for the next request

Testing

Use session.Inject to set up session state, then call messages.Add and read back with messages.Get:

func TestFlashMessage(t *testing.T) {
    rec := httptest.NewRecorder()
    req := httptest.NewRequest(http.MethodGet, "/", nil)
    req = session.Inject(req, map[string]any{})

    err := messages.AddSuccess(rec, req, "Saved")
    require.NoError(t, err)

    values := session.GetValues(req)
    msgs := values["_messages"].([]messages.Message)
    assert.Equal(t, messages.Success, msgs[0].Level)
    assert.Equal(t, "Saved", msgs[0].Text)
}

For template tests that need messages in context, use messages.Inject:

ctx := messages.Inject(context.Background(), []messages.Message{
    {Level: messages.Success, Text: "Done"},
})
// Render template with ctx

Interfaces Implemented

Interface Description
burrow.App Required: Name()
burrow.HasMiddleware Flash message middleware (read from session, inject into context, clear)
burrow.HasRequestFuncMap Provides the messages template function used in layouts
burrow.HasDependencies Declares dependency on session