Skip to main content
An App is the main entry point for your web server. It wraps an HTTP server, manages its lifecycle, and handles graceful shutdown when the process receives a stop signal.

What an App does

When you create an app with mizu.New(), you get:
ComponentDescription
RouterEmbedded, so you can call app.Get(), app.Post() directly
LoggerRequest logging enabled by default
Panic recoveryCatches panics in handlers, prevents crashes
Graceful shutdownDrains active requests before stopping

Create an app

Every Mizu project starts by creating an App:
package main

import "github.com/go-mizu/mizu"

func main() {
    // Create a new app with default settings
    app := mizu.New()

    // Define a route - App embeds Router, so this works directly
    app.Get("/", func(c *mizu.Ctx) error {
        return c.Text(200, "Hello, Mizu!")
    })

    // Start the HTTP server on port 3000
    // This blocks until you press Ctrl+C
    app.Listen(":3000")
}
When this runs:
  1. mizu.New() creates an app with a router and default logger
  2. app.Get("/", ...) registers a handler for GET requests to ”/”
  3. app.Listen(":3000") starts the HTTP server
The terminal shows startup logs:
INFO server starting addr=:3000 pid=12345 go_version=go1.22

Starting the server

Mizu provides three ways to start your server:
// 1. Listen on a TCP address (most common)
app.Listen(":3000")

// 2. Listen with TLS (HTTPS)
app.ListenTLS(":443", "cert.pem", "key.pem")

// 3. Use an existing listener (advanced)
listener, _ := net.Listen("tcp", ":3000")
app.Serve(listener)
All three methods block until the server stops. They also handle graceful shutdown when the process receives SIGINT (Ctrl+C) or SIGTERM.

Configuration

Configure the app by setting fields after creation:
app := mizu.New()

// Shutdown timeout: how long to wait for active requests
// Default: 15 seconds
app.ShutdownTimeout = 30 * time.Second
FieldDefaultDescription
ShutdownTimeout15 secondsMaximum time to wait for active requests to complete during shutdown

Graceful shutdown

When your app receives a stop signal (SIGINT or SIGTERM):
  1. New connections refused - The server stops accepting new connections
  2. Active requests complete - Running requests continue until finished (up to ShutdownTimeout)
  3. Clean exit - Server exits with success status
This prevents dropped connections during deployments. Your load balancer can redirect traffic while the old instance drains.
INFO shutdown initiated
INFO server stopped gracefully duration=1.234s
If requests don’t finish within ShutdownTimeout, they’re terminated.

Health check endpoints

Mizu provides two health check handlers for load balancers and orchestrators like Kubernetes:
app := mizu.New()

// Liveness probe: is the process alive?
// Always returns 200 OK
http.Handle("/livez", app.LivezHandler())

// Readiness probe: can it handle traffic?
// Returns 200 normally, 503 during shutdown
http.Handle("/readyz", app.ReadyzHandler())

app.Listen(":3000")
EndpointNormalDuring Shutdown
/livez200 OK200 OK
/readyz200 OK503 Service Unavailable
How they differ:
  • Liveness (/livez): “Is the process running?” Used by Kubernetes to decide if the pod should be restarted
  • Readiness (/readyz): “Can it handle traffic?” Used by load balancers to route traffic
During graceful shutdown, /readyz returns 503 so load balancers stop sending new traffic, while /livez continues returning 200 so the pod isn’t killed prematurely.

Access the underlying server

For advanced configuration, access the http.Server instance:
app := mizu.New()

// Configure routes...
app.Get("/", handler)

// Access the server (available after calling Listen/ListenTLS/Serve)
// Can be used to configure TLSConfig, ConnState callbacks, etc.
srv := app.Server()

Complete example

package main

import (
    "net/http"
    "time"

    "github.com/go-mizu/mizu"
)

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

    // Configure shutdown timeout
    app.ShutdownTimeout = 30 * time.Second

    // Health check endpoints
    http.Handle("/livez", app.LivezHandler())
    http.Handle("/readyz", app.ReadyzHandler())

    // Your API routes
    app.Get("/", func(c *mizu.Ctx) error {
        return c.JSON(200, map[string]string{"status": "ok"})
    })

    app.Group("/api", func(r *mizu.Router) {
        r.Get("/users", listUsers)
        r.Post("/users", createUser)
    })

    // Set error handler
    app.ErrorHandler(func(c *mizu.Ctx, err error) {
        c.Logger().Error("request failed", "error", err)
        c.JSON(500, map[string]string{"error": "internal error"})
    })

    // Start server
    app.Listen(":3000")
}

func listUsers(c *mizu.Ctx) error { return c.JSON(200, []string{}) }
func createUser(c *mizu.Ctx) error { return c.JSON(201, nil) }

Next steps