<h1>New Article</h1>
<form action="/articles" method="post">
<div>
<label for="title">Title</label>
<input id="title" type="text" name="article[title]"
value="<%= @article.title %>">
</div>
<div>
<label for="body">Body</label>
<textarea id="body" name="article[body]"><%= @article.body %></textarea>
</div>
<button type="submit">Publish</button>
</form>
.flash {
margin: 0 0 1rem;
padding: 0.75rem 1rem;
border-radius: 6px;
font-size: 0.95rem;
}
.flash-success {
background: #e6f4ea;
color: #1e7d34;
border: 1px solid #bfe3c8;
}
.flash-notice {
background: #eef2ff;
color: #3b3f9c;
border: 1px solid #cdd4fb;
}
.flash-error {
background: #fdeaea;
color: #b3261e;
border: 1px solid #f5c2c0;
}
require 'sinatra'
require 'sinatra/flash'
class Blog < Sinatra::Base
enable :sessions
set :session_secret, ENV.fetch('SESSION_SECRET', 'change-me-in-production')
register Sinatra::Flash
helpers do
def redirect_to(path, type = nil, message = nil, status: 303)
flash[type] = message if type && message
redirect to(path), status
end
end
get '/articles' do
@articles = Article.order(created_at: :desc)
erb :index
end
get '/articles/new' do
@article = Article.new
erb :new
end
post '/articles' do
@article = Article.new(params[:article])
if @article.save
redirect_to '/articles', :success, 'Article published.'
else
flash.now[:error] = @article.errors.full_messages.join(', ')
status 422
erb :new
end
end
post '/articles/:id/delete' do
Article.find(params[:id]).destroy
redirect_to '/articles', :notice, 'Article removed.'
end
end
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Blog</title>
<link rel="stylesheet" href="/flash.css">
</head>
<body>
<% flash.keys.each do |type| %>
<div class="flash flash-<%= type %>" role="alert">
<%= flash[type] %>
</div>
<% end %>
<main>
<%= yield %>
</main>
</body>
</html>
This snippet demonstrates the Post-Redirect-Get (PRG) pattern in a classic Sinatra application, using sinatra-flash to carry one-shot messages across the redirect. PRG solves a well-known problem: if a browser renders HTML directly in the response to a POST, then a refresh or back-button re-submits the form, potentially creating duplicate records. The fix is to respond to a successful POST with a 303 See Other (or 302) redirect to a GET route, so the reloadable URL is idempotent.
The app.rb tab wires up the moving parts. enable :sessions is required because flash storage lives in the session, and register Sinatra::Flash adds the flash helper. The custom redirect_to helper centralizes the redirect convention: it accepts a path, an optional flash type and message, sets flash[type] only when a message is present, and issues a redirect using 303 by default so the follow-up request is always a GET. Keeping this in one helper means every controller action redirects consistently instead of hand-rolling flash[:success] = ...; redirect ... everywhere.
The POST /articles route shows the pattern end to end. It builds an Article, and on save it calls redirect_to '/articles', :success, '...'; on failure it re-renders the form inline with flash.now[:error], which is the important distinction — flash.now is visible only in the current request and is not persisted to the session, so it does not leak onto the next page. The GET routes then read flash[:success] once; sinatra-flash deletes the value after it is read, giving true single-use messages.
The layout.erb tab renders whatever flash keys exist by iterating flash.keys, mapping each type to a CSS class. Because the layout is shared, any action that sets a flash gets a rendered banner for free. A subtle pitfall worth noting: flash relies on a working session cookie, so set :session_secret should be configured in real deployments, and redirects must point at same-origin paths. This structure keeps forms safe against double submits while giving clean, consistent user feedback.
Related snips
require "csv"
class PeopleCsvStream
include Enumerable
HEADERS = %w[id full_name email signed_up_at plan].freeze
Resilient CSV Export as a Streamed Response
class SignupForm
include ActiveModel::Model
include ActiveModel::Attributes
attribute :account_name, :string
attribute :email, :string
Shallow Controller, Deep Params: Form Object Pattern
from django.urls import path
from . import views
app_name = 'blog'
urlpatterns = [
Django URL namespacing and reverse lookups
import { randomBytes, createHash } from "crypto";
import jwt from "jsonwebtoken";
import { RefreshTokenStore } from "./store";
const ACCESS_SECRET = process.env.ACCESS_SECRET!;
const ACCESS_TTL = "15m";
JWT access + refresh token rotation (conceptual)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Form Validation Example</title>
<style>
HTML forms with validation and accessibility
import { Application } from "@hotwired/stimulus"
import FormSubmitController from "./controllers/form_submit_controller"
const application = Application.start()
application.debug = false
Disable submit button while Turbo form is submitting
Share this code
Here's the card — post it anywhere.