Skip to main content

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

LayerResponsibilityDependencies
cmdStart the appinternal, pkg
handlerHTTP request/responseservice
serviceBusiness rulesrepository, domain
repositoryDatabase operationsdomain
domainCore typesnone

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

  1. Keep main.go small - Only setup and wiring
  2. Handlers are thin - Extract business logic to services
  3. One package per concern - Don’t mix handlers with models
  4. Use interfaces at boundaries - Makes testing easier
  5. Prefer explicit over implicit - Pass dependencies, don’t use globals
  6. 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.