🍣 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) {
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 and Queries
Sushi provides a fluent API for accessing URL parameters, query strings, and form data with automatic type conversion:
URL Parameters (Named Routes)
app.Get("/users/:id", func(ctx sushi.Ctx) {
// Get URL parameter with fluent API
userID := ctx.Param("id").Int() // /users/123 -> 123
userIDStr := ctx.Param("id").String() // /users/123 -> "123"
// With defaults
limit := ctx.Param("limit").IntDefault(10)
})
app.Get("/users/:id/posts/:slug", func(ctx sushi.Ctx) {
userID := ctx.Param("id").Int()
slug := ctx.Param("slug").String()
// Check if parameter exists
if ctx.Param("optional").Exists() {
// Handle optional parameter
}
})
Query Parameters
app.Get("/search", func(ctx sushi.Ctx) {
// Get query parameters with fluent API
query := ctx.Query("q").String() // ?q=hello -> "hello"
page := ctx.Query("page").IntDefault(1) // ?page=2 -> 2
limit := ctx.Query("limit").IntDefault(20) // Default to 20 if not present
sortBy := ctx.Query("sort").StringDefault("name") // Default to "name"
// Boolean query params
includeDeleted := ctx.Query("deleted").Bool() // ?deleted=true -> true
// Float values
minPrice := ctx.Query("min_price").Float() // ?min_price=19.99 -> 19.99
// Check existence
if ctx.Query("filter").Exists() {
filter := ctx.Query("filter").String()
// Apply filter
}
})
Form Data
Access form data with the fluent API:
app.Post("/users", func(ctx sushi.Ctx) {
// Get form fields with fluent API
name := ctx.Input("name").String()
email := ctx.Input("email").String()
age := ctx.Input("age").IntDefault(0)
// Check if checkbox is checked
agreeToTerms := ctx.Input("terms").Bool() // Checks for "true", "on", "1", "yes"
// Validate required fields
if ctx.Input("email").IsEmpty() {
ctx.SendError(400, "Email is required")
return
}
// Get array of values (for multiple select, checkboxes)
tags := ctx.GetFormArray("tags[]")
})
Response Helpers
func myHandler(ctx sushi.Ctx) {
// 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)
// File responses
ctx.SendFile("/path/to/file.pdf")
// Raw bytes
ctx.SendBytes(imageData, "image/png")
}
## Middleware
```go
// Custom middleware
func loggingMiddleware() sushi.Middleware {
return func(ctx sushi.Ctx, 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. 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) {
// Use fluent API for form data
email := ctx.Input("email").String()
password := ctx.Input("password").String()
// Validate inputs
if ctx.Input("email").IsEmpty() || ctx.Input("password").IsEmpty() {
ctx.SendError(400, "Email and password are required")
return
}
// 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) {
ctx.Logout()
ctx.Redirect("/")
}
6. Getting Current User
func dashboardHandler(ctx sushi.Ctx) {
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) {
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) {
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) {
if ctx.IsAuthenticated() {
ctx.Redirect("/dashboard")
return
}
ctx.SendHTML(`<a href="/login">Login</a>`)
}
func loginPageHandler(ctx sushi.Ctx) {
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) {
// Use fluent API for form data
email := ctx.Input("email").String()
pass := ctx.Input("password").String()
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) {
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) {
ctx.Logout()
ctx.Redirect("/")
}
Description
Languages
Go
100%