🍣 Sushi

A fast, raw, tasty framework for simplifying basic web apps!

Quick Start

package main

import "git.sharkk.net/Sharkk/Sushi"

func main() {
	app := sushi.New()

	// Initialize sessions
	sushi.InitSessions("sessions.json")

	app.Get("/", func(ctx sushi.Ctx, params []any) {
		ctx.SendHTML("<h1>Hello Sushi!</h1>")
	})

	app.Listen(":8080")
}

Routing

// Basic routes
app.Get("/users/:id", getUserHandler)
app.Post("/users", createUserHandler)
app.Put("/users/:id", updateUserHandler)
app.Delete("/users/:id", deleteUserHandler)

// Wildcards
app.Get("/files/*path", serveFilesHandler)

// Route groups
api := app.Group("/api/v1")
api.Get("/users", listUsersHandler)
api.Post("/users", createUserHandler)

Parameters

URL parameters are automatically converted to the appropriate type:

// Numeric parameters become integers
app.Get("/users/:id", func(ctx sushi.Ctx, params []any) {
	userID := params[0].(int)  // /users/123 -> 123
	// ...
})

// String parameters stay strings
app.Get("/users/:name", func(ctx sushi.Ctx, params []any) {
	name := params[0].(string)  // /users/john -> "john"
	// ...
})

// Mixed types
app.Get("/users/:id/posts/:slug", func(ctx sushi.Ctx, params []any) {
	userID := params[0].(int)     // 123
	slug := params[1].(string)    // "my-post"
	// ...
})

Response Helpers

func myHandler(ctx sushi.Ctx, params []any) {
	// JSON responses
	ctx.SendJSON(map[string]string{"message": "success"})

	// HTML responses
	ctx.SendHTML("<h1>Welcome</h1>")

	// Text responses
	ctx.SendText("Plain text")

	// Error responses
	ctx.SendError(404, "Not Found")

	// Redirects
	ctx.Redirect("/login")

	// Status only
	ctx.SendStatus(204)
}

Middleware

// Custom middleware
func loggingMiddleware() sushi.Middleware {
	return func(ctx sushi.Ctx, params []any, next func()) {
		println("Request:", string(ctx.Method()), string(ctx.Path()))
		next()
		println("Status:", ctx.Response.StatusCode())
	}
}

app.Use(loggingMiddleware())

// Group middleware
admin := app.Group("/admin")
admin.Use(auth.RequireAuth("/login"))

Authentication Workflow

1. Setup Password Hashing

import "git.sharkk.net/Sharkk/Sushi/password"

// Hash password for storage
hashedPassword := password.HashPassword("userpassword123")

// Verify password during login
isValid, err := password.VerifyPassword("userpassword123", hashedPassword)

2. User Structure

type User struct {
	ID       int    `json:"id"`
	Email    string `json:"email"`
	Username string `json:"username"`
	Password string `json:"password"` // Store hashed password
}

// User lookup function for auth middleware
func getUserByID(userID int) any {
	// Query your database for user by ID
	// Return nil if not found
	return &User{ID: userID, Email: "user@example.com"}
}

3. Session & Auth Middleware

import (
	"git.sharkk.net/Sharkk/Sushi/session"
	"git.sharkk.net/Sharkk/Sushi/auth"
)

func main() {
	app := sushi.New()

	// Initialize sessions
	sushi.InitSessions("sessions.json")

	// Add session middleware
	app.Use(session.Middleware())

	// Add auth middleware with user lookup
	app.Use(auth.Middleware(getUserByID))

	// Public routes
	app.Get("/login", loginPageHandler)
	app.Post("/login", loginHandler)
	app.Post("/logout", logoutHandler)

	// Protected routes
	protected := app.Group("/dashboard")
	protected.Use(auth.RequireAuth("/login"))
	protected.Get("/", dashboardHandler)
}

4. Login Handler

func loginHandler(ctx sushi.Ctx, params []any) {
	email := string(ctx.PostArgs().Peek("email"))
	password := string(ctx.PostArgs().Peek("password"))

	// Find user by email/username
	user := findUserByEmail(email)
	if user == nil {
		ctx.SendError(401, "Invalid credentials")
		return
	}

	// Verify password
	isValid, err := password.VerifyPassword(password, user.Password)
	if err != nil || !isValid {
		ctx.SendError(401, "Invalid credentials")
		return
	}

	// Log the user in
	ctx.Login(user.ID, user)

	ctx.Redirect("/dashboard")
}

5. Logout Handler

func logoutHandler(ctx sushi.Ctx, params []any) {
	ctx.Logout()
	ctx.Redirect("/")
}

6. Getting Current User

func dashboardHandler(ctx sushi.Ctx, params []any) {
	user := ctx.GetCurrentUser().(*User)

	html := fmt.Sprintf("<h1>Welcome, %s!</h1>", user.Username)
	ctx.SendHTML(html)
}

CSRF Protection

import "git.sharkk.net/Sharkk/Sushi/csrf"

// Add CSRF middleware to forms
app.Use(csrf.Middleware())

// In your form template
func loginPageHandler(ctx sushi.Ctx, params []any) {
	csrfField := csrf.CSRFHiddenField(ctx)

	html := fmt.Sprintf(`
		<form method="POST" action="/login">
			%s
			<input type="email" name="email" required>
			<input type="password" name="password" required>
			<button type="submit">Login</button>
		</form>
	`, csrfField)

	ctx.SendHTML(html)
}

Static Files

// Serve static files
app.Get("/static/*path", sushi.Static("./public"))

// Serve single file
app.Get("/favicon.ico", sushi.StaticFile("./public/favicon.ico"))

// Embedded files
files := map[string][]byte{
	"/style.css": cssData,
	"/app.js": jsData,
}
app.Get("/assets/*path", sushi.StaticEmbed(files))

Sessions

func someHandler(ctx sushi.Ctx, params []any) {
	sess := ctx.GetCurrentSession()

	// Set session data
	sess.Set("user_preference", "dark_mode")

	// Get session data
	if pref, exists := sess.Get("user_preference"); exists {
		preference := pref.(string)
	}

	// Flash messages (one-time)
	sess.SetFlash("success", "Profile updated!")

	// Get flash message
	message := sess.GetFlashMessage("success")
}

Server Configuration

app := sushi.New(sushi.ServerOptions{
	ReadTimeout:        30 * time.Second,
	WriteTimeout:       30 * time.Second,
	MaxRequestBodySize: 10 * 1024 * 1024, // 10MB
})

// TLS
app.ListenTLS(":443", "cert.pem", "key.pem")

Complete Auth Example

package main

import (
	"fmt"
	sushi "git.sharkk.net/Sharkk/Sushi"
	"git.sharkk.net/Sharkk/Sushi/auth"
	"git.sharkk.net/Sharkk/Sushi/csrf"
	"git.sharkk.net/Sharkk/Sushi/password"
	"git.sharkk.net/Sharkk/Sushi/session"
)

type User struct {
	ID       int
	Email    string
	Password string
}

var users = map[int]*User{
	1: {ID: 1, Email: "admin@example.com", Password: password.HashPassword("admin123")},
}

func getUserByID(userID int) any {
	return users[userID]
}

func findUserByEmail(email string) *User {
	for _, user := range users {
		if user.Email == email {
			return user
		}
	}
	return nil
}

func main() {
	app := sushi.New()

	sushi.InitSessions("sessions.json")
	app.Use(session.Middleware())
	app.Use(auth.Middleware(getUserByID))

	// Public routes
	app.Get("/", homeHandler)
	app.Get("/login", loginPageHandler)
	app.Post("/login", loginHandler)

	// Protected routes
	protected := app.Group("/dashboard")
	protected.Use(auth.RequireAuth("/login"))
	protected.Use(csrf.Middleware())
	protected.Get("/", dashboardHandler)
	protected.Post("/logout", logoutHandler)

	app.Listen(":8080")
}

func homeHandler(ctx sushi.Ctx, params []any) {
	if ctx.IsAuthenticated() {
		ctx.Redirect("/dashboard")
		return
	}
	ctx.SendHTML(`<a href="/login">Login</a>`)
}

func loginPageHandler(ctx sushi.Ctx, params []any) {
	html := fmt.Sprintf(`
		<form method="POST" action="/login">
			%s
			<input type="email" name="email" placeholder="Email" required><br>
			<input type="password" name="password" placeholder="Password" required><br>
			<button type="submit">Login</button>
		</form>
	`, csrf.HiddenField(ctx))

	ctx.SendHTML(html)
}

func loginHandler(ctx sushi.Ctx, params []any) {
	email := string(ctx.PostArgs().Peek("email"))
	pass := string(ctx.PostArgs().Peek("password"))

	user := findUserByEmail(email)
	if user == nil {
		ctx.SendError(401, "Invalid credentials")
		return
	}

	if valid, _ := password.VerifyPassword(pass, user.Password); !valid {
		ctx.SendError(401, "Invalid credentials")
		return
	}

	ctx.Login(user.ID, user)
	ctx.Redirect("/dashboard")
}

func dashboardHandler(ctx sushi.Ctx, params []any) {
	user := ctx.GetCurrentUser().(*User)

	html := fmt.Sprintf(`
		<h1>Welcome, %s!</h1>
		<form method="POST" action="/logout">
			%s
			<button type="submit">Logout</button>
		</form>
	`, user.Email, csrf.HiddenField(ctx))

	ctx.SendHTML(html)
}

func logoutHandler(ctx sushi.Ctx, params []any) {
	ctx.Logout()
	ctx.Redirect("/")
}
Description
Raw, tasty framework for web apps.
Readme 89 KiB
2025-08-22 08:47:18 -05:00
Languages
Go 100%