Documentation Index
Fetch the complete documentation index at: https://docs.go-mizu.dev/llms.txt
Use this file to discover all available pages before exploring further.
This guide presents recommended patterns for structuring Mizu projects, from simple scripts to production applications.
Simple projects
For small projects or prototypes, everything can live in a single file:
myapp/
βββ main.go
βββ go.mod
βββ go.sum
// main.go
package main
import "github.com/go-mizu/mizu"
func main() {
app := mizu.New()
app.Get("/", func(c *mizu.Ctx) error {
return c.JSON(200, map[string]string{"status": "ok"})
})
app.Listen(":3000")
}
This is fine for scripts, tools, and learning. No ceremony needed.
Standard layout
For larger applications, separate concerns into packages:
myapp/
βββ main.go # Entry point
βββ go.mod
βββ go.sum
βββ handlers/ # HTTP handlers
β βββ users.go
β βββ posts.go
βββ services/ # Business logic
β βββ user_service.go
β βββ post_service.go
βββ models/ # Data structures
β βββ user.go
β βββ post.go
βββ middleware/ # Custom middleware
β βββ auth.go
βββ config/ # Configuration
βββ config.go
main.go
The entry point sets up the app and routes:
package main
import (
"myapp/config"
"myapp/handlers"
"myapp/middleware"
"github.com/go-mizu/mizu"
)
func main() {
cfg := config.Load()
app := mizu.New()
// Global middleware
app.Use(middleware.RequestID())
// Public routes
app.Get("/health", handlers.Health)
// API routes
api := app.Group("/api")
api.Get("/users", handlers.ListUsers)
api.Get("/users/{id}", handlers.GetUser)
api.Post("/users", handlers.CreateUser)
// Protected routes
protected := api.With(middleware.RequireAuth())
protected.Put("/users/{id}", handlers.UpdateUser)
protected.Delete("/users/{id}", handlers.DeleteUser)
app.Listen(":" + cfg.Port)
}
handlers/users.go
Handlers focus on HTTP concerns:
package handlers
import (
"myapp/models"
"myapp/services"
"github.com/go-mizu/mizu"
)
func ListUsers(c *mizu.Ctx) error {
users, err := services.Users.List(c.Context())
if err != nil {
return err
}
return c.JSON(200, users)
}
func GetUser(c *mizu.Ctx) error {
id := c.Param("id")
user, err := services.Users.Get(c.Context(), id)
if err != nil {
return err
}
return c.JSON(200, user)
}
func CreateUser(c *mizu.Ctx) error {
var input models.CreateUserInput
if err := c.BindJSON(&input, 1<<20); err != nil {
return err
}
user, err := services.Users.Create(c.Context(), input)
if err != nil {
return err
}
return c.JSON(201, user)
}
services/user_service.go
Services contain business logic:
package services
import (
"context"
"myapp/models"
)
var Users = &UserService{}
type UserService struct {
// Add dependencies here (DB, cache, etc.)
}
func (s *UserService) List(ctx context.Context) ([]models.User, error) {
// Business logic here
return []models.User{}, nil
}
func (s *UserService) Get(ctx context.Context, id string) (*models.User, error) {
// Business logic here
return &models.User{ID: id}, nil
}
func (s *UserService) Create(ctx context.Context, input models.CreateUserInput) (*models.User, error) {
// Validation, database insert, etc.
return &models.User{ID: "new", Name: input.Name}, nil
}
models/user.go
Models define data structures:
package models
import "time"
type User struct {
ID string `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
CreatedAt time.Time `json:"createdAt"`
}
type CreateUserInput struct {
Name string `json:"name"`
Email string `json:"email"`
}
Clean architecture
For complex applications, use a layered architecture:
myapp/
βββ cmd/
β βββ server/
β βββ main.go # Entry point
βββ internal/
β βββ app/
β β βββ app.go # App setup
β βββ handler/ # HTTP handlers
β β βββ handler.go
β β βββ user.go
β βββ service/ # Business logic
β β βββ user.go
β βββ repository/ # Data access
β β βββ user.go
β βββ domain/ # Core types
β βββ user.go
βββ pkg/ # Public packages
β βββ validate/
β βββ validate.go
βββ go.mod
Benefits
| Layer | Responsibility | Dependencies |
|---|
| cmd | Start the app | internal, pkg |
| handler | HTTP request/response | service |
| service | Business rules | repository, domain |
| repository | Database operations | domain |
| domain | Core types | none |
Dependency injection
Pass dependencies explicitly:
// internal/app/app.go
package app
import (
"database/sql"
"myapp/internal/handler"
"myapp/internal/repository"
"myapp/internal/service"
"github.com/go-mizu/mizu"
)
func New(db *sql.DB) *mizu.App {
// Build dependency graph
userRepo := repository.NewUserRepository(db)
userService := service.NewUserService(userRepo)
userHandler := handler.NewUserHandler(userService)
// Create app
app := mizu.New()
// Register routes
app.Get("/users", userHandler.List)
app.Get("/users/{id}", userHandler.Get)
app.Post("/users", userHandler.Create)
return app
}
Static files and templates
Include static files and templates:
myapp/
βββ main.go
βββ static/ # Static assets
β βββ css/
β βββ js/
β βββ images/
βββ templates/ # HTML templates
β βββ layout.html
β βββ pages/
β βββ home.html
β βββ about.html
βββ embed.go # Embed directive
embed.go
package main
import "embed"
//go:embed static/*
var StaticFS embed.FS
//go:embed templates/*
var TemplateFS embed.FS
main.go
package main
import (
"io/fs"
"net/http"
"github.com/go-mizu/mizu"
)
func main() {
app := mizu.New()
// Serve static files
static, _ := fs.Sub(StaticFS, "static")
app.Static("/static/", http.FS(static))
// Your routes
app.Get("/", homeHandler)
app.Listen(":3000")
}
Configuration
Use environment variables with sensible defaults:
// config/config.go
package config
import "os"
type Config struct {
Port string
DatabaseURL string
Debug bool
}
func Load() *Config {
return &Config{
Port: getEnv("PORT", "3000"),
DatabaseURL: getEnv("DATABASE_URL", ""),
Debug: getEnv("DEBUG", "false") == "true",
}
}
func getEnv(key, fallback string) string {
if value := os.Getenv(key); value != "" {
return value
}
return fallback
}
Testing
Keep tests next to the code they test:
handlers/
βββ users.go
βββ users_test.go
βββ posts.go
βββ posts_test.go
Or use a separate test directory for integration tests:
myapp/
βββ handlers/
βββ services/
βββ tests/
βββ integration/
β βββ api_test.go
βββ e2e/
βββ full_flow_test.go
Best practices
- Keep main.go small - Only setup and wiring
- Handlers are thin - Extract business logic to services
- One package per concern - Donβt mix handlers with models
- Use interfaces at boundaries - Makes testing easier
- Prefer explicit over implicit - Pass dependencies, donβt use globals
- Group related routes - Use
app.Group() for organization
Anti-patterns to avoid
- God packages -
utils, helpers, common
- Circular imports - Usually means wrong package boundaries
- Business logic in handlers - Keep handlers focused on HTTP
- Global state - Makes testing difficult
Next steps
Testing
Write tests for your handlers.
Deployment
Deploy to production.