Skip to main content
The Contract system lets you define your API once and serve it via multiple transports (REST, JSON-RPC, MCP). It also generates type-safe SDKs for Go, Python, and TypeScript.

What is a Contract?

A Contract is a Go struct that defines your service’s methods. Mizu extracts method signatures via reflection and generates handlers, OpenAPI specs, and client SDKs automatically.
// Define your service as a Go struct
type UserService struct {
    db *sql.DB
}

// Each method becomes an API endpoint
func (s *UserService) GetUser(ctx context.Context, id string) (*User, error) {
    return s.db.FindUser(ctx, id)
}

func (s *UserService) CreateUser(ctx context.Context, input CreateUserInput) (*User, error) {
    return s.db.CreateUser(ctx, input)
}

Why Use Contracts?

Type Safety Across Boundaries

Your API types are defined once in Go and shared with all clients:
// Server (Go)
type User struct {
    ID    string `json:"id"`
    Name  string `json:"name"`
    Email string `json:"email"`
}

// Generated Python SDK
class User:
    id: str
    name: str
    email: str

// Generated TypeScript SDK
interface User {
    id: string;
    name: string;
    email: string;
}

Multiple Transports from One Definition

service := &UserService{db: db}
contract := contract.New(service)

// Serve as REST
app.Mount("/api", rest.Handler(contract))

// Serve as JSON-RPC
app.Mount("/rpc", jsonrpc.Handler(contract))

// Serve as MCP (for AI tools)
app.Mount("/mcp", mcp.Handler(contract))

// Generate OpenAPI spec
spec := openapi.Generate(contract)

Automatic Client Generation

# Generate Go SDK
mizu contract sdk go --output ./sdk/go

# Generate Python SDK
mizu contract sdk python --output ./sdk/python

# Generate TypeScript SDK
mizu contract sdk typescript --output ./sdk/typescript

Quick Example

1. Define the Service

package main

import (
    "context"
    "github.com/go-mizu/mizu"
    "github.com/go-mizu/mizu/contract/rest"
)

// User model
type User struct {
    ID    string `json:"id"`
    Name  string `json:"name"`
    Email string `json:"email"`
}

// Input for creating users
type CreateUserInput struct {
    Name  string `json:"name"`
    Email string `json:"email"`
}

// Service definition
type UserService struct{}

func (s *UserService) GetUser(ctx context.Context, id string) (*User, error) {
    return &User{ID: id, Name: "Alice", Email: "[email protected]"}, nil
}

func (s *UserService) ListUsers(ctx context.Context) ([]*User, error) {
    return []*User{
        {ID: "1", Name: "Alice", Email: "[email protected]"},
        {ID: "2", Name: "Bob", Email: "[email protected]"},
    }, nil
}

func (s *UserService) CreateUser(ctx context.Context, input CreateUserInput) (*User, error) {
    return &User{
        ID:    "new-id",
        Name:  input.Name,
        Email: input.Email,
    }, nil
}

2. Register and Serve

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

    service := &UserService{}
    contract := contract.Register(service)

    // Mount as REST API
    app.Mount("/api", rest.Handler(contract))

    app.Listen(":3000")
}

3. Use the API

# Get a user
curl http://localhost:3000/api/users/1

# List users
curl http://localhost:3000/api/users

# Create a user
curl -X POST http://localhost:3000/api/users \
  -H "Content-Type: application/json" \
  -d '{"name": "Charlie", "email": "[email protected]"}'

Supported Transports

REST (HTTP/JSON)

The most common transport, using standard HTTP methods:
import "github.com/go-mizu/mizu/contract/rest"

app.Mount("/api", rest.Handler(contract))
Method PrefixHTTP MethodExample
Get*GETGetUser → GET /users/{id}
List*GETListUsers → GET /users
Create*POSTCreateUser → POST /users
Update*PUTUpdateUser → PUT /users/{id}
Delete*DELETEDeleteUser → DELETE /users/{id}

JSON-RPC

For RPC-style APIs:
import "github.com/go-mizu/mizu/contract/jsonrpc"

app.Mount("/rpc", jsonrpc.Handler(contract))
curl -X POST http://localhost:3000/rpc \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","method":"UserService.GetUser","params":["1"],"id":1}'

MCP (Model Context Protocol)

For AI tool integration (Claude, etc.):
import "github.com/go-mizu/mizu/contract/mcp"

app.Mount("/mcp", mcp.Handler(contract))
This allows AI models to call your API methods as tools.

OpenAPI

Generate OpenAPI 3.0 specifications:
import "github.com/go-mizu/mizu/contract/openapi"

spec := openapi.Generate(contract)
app.Get("/openapi.json", func(c *mizu.Ctx) error {
    return c.JSON(200, spec)
})

SDK Generation

Generate type-safe clients for multiple languages:

Go SDK

mizu contract sdk go --output ./sdk/go
// Using generated Go SDK
client := usersdk.New("http://localhost:3000/api")
user, err := client.GetUser(ctx, "1")

Python SDK

mizu contract sdk python --output ./sdk/python
# Using generated Python SDK
from usersdk import UserClient

client = UserClient("http://localhost:3000/api")
user = client.get_user("1")

TypeScript SDK

mizu contract sdk typescript --output ./sdk/typescript
// Using generated TypeScript SDK
import { UserClient } from './usersdk';

const client = new UserClient('http://localhost:3000/api');
const user = await client.getUser('1');

Comparison with Alternatives

vs. gRPC

AspectgRPCMizu Contract
Schema.proto filesGo structs
ProtocolHTTP/2 + ProtobufHTTP/1.1 + JSON (or HTTP/2)
Browser supportNeeds proxyNative
TransportsOne (gRPC)Multiple (REST, JSON-RPC, MCP)
Learning curveSteeperGentler
PerformanceFasterFast enough for most

vs. OpenAPI-First

AspectOpenAPI-FirstMizu Contract
Source of truthYAML/JSON specGo code
Type safetyGeneratedNative
RefactoringManual syncAutomatic
ToolingExternalBuilt-in

vs. GraphQL

AspectGraphQLMizu Contract
Query flexibilityHighFixed endpoints
ComplexityHigherLower
CachingHarderStandard HTTP
Client generationExternalBuilt-in

Method Conventions

Naming

Method names determine HTTP methods and routes:
// GET /users/{id}
func (s *UserService) GetUser(ctx context.Context, id string) (*User, error)

// GET /users
func (s *UserService) ListUsers(ctx context.Context) ([]*User, error)

// POST /users
func (s *UserService) CreateUser(ctx context.Context, input CreateInput) (*User, error)

// PUT /users/{id}
func (s *UserService) UpdateUser(ctx context.Context, id string, input UpdateInput) (*User, error)

// DELETE /users/{id}
func (s *UserService) DeleteUser(ctx context.Context, id string) error

Parameters

Parameter TypeMapping
ctx context.ContextRequest context (required first)
string, int, etc.Path parameters
StructRequest body (JSON)

Return Values

Return TypeMapping
errorError response
(*T, error)JSON response
([]*T, error)JSON array response

Error Handling

Return errors to send error responses:
import "github.com/go-mizu/mizu/contract"

func (s *UserService) GetUser(ctx context.Context, id string) (*User, error) {
    user, err := s.db.FindUser(ctx, id)
    if err != nil {
        if errors.Is(err, sql.ErrNoRows) {
            // Returns 404 Not Found
            return nil, contract.NotFound("user not found")
        }
        // Returns 500 Internal Server Error
        return nil, err
    }
    return user, nil
}
Built-in error types:
  • contract.NotFound(msg) → 404
  • contract.BadRequest(msg) → 400
  • contract.Unauthorized(msg) → 401
  • contract.Forbidden(msg) → 403

Complete Example

package main

import (
    "context"
    "errors"

    "github.com/go-mizu/mizu"
    "github.com/go-mizu/mizu/contract"
    "github.com/go-mizu/mizu/contract/rest"
    "github.com/go-mizu/mizu/contract/openapi"
)

// Models
type Task struct {
    ID        string `json:"id"`
    Title     string `json:"title"`
    Completed bool   `json:"completed"`
}

type CreateTaskInput struct {
    Title string `json:"title"`
}

type UpdateTaskInput struct {
    Title     *string `json:"title,omitempty"`
    Completed *bool   `json:"completed,omitempty"`
}

// Service
type TaskService struct {
    tasks map[string]*Task
}

func (s *TaskService) ListTasks(ctx context.Context) ([]*Task, error) {
    result := make([]*Task, 0, len(s.tasks))
    for _, t := range s.tasks {
        result = append(result, t)
    }
    return result, nil
}

func (s *TaskService) GetTask(ctx context.Context, id string) (*Task, error) {
    task, ok := s.tasks[id]
    if !ok {
        return nil, contract.NotFound("task not found")
    }
    return task, nil
}

func (s *TaskService) CreateTask(ctx context.Context, input CreateTaskInput) (*Task, error) {
    if input.Title == "" {
        return nil, contract.BadRequest("title is required")
    }
    task := &Task{
        ID:    generateID(),
        Title: input.Title,
    }
    s.tasks[task.ID] = task
    return task, nil
}

func (s *TaskService) UpdateTask(ctx context.Context, id string, input UpdateTaskInput) (*Task, error) {
    task, ok := s.tasks[id]
    if !ok {
        return nil, contract.NotFound("task not found")
    }
    if input.Title != nil {
        task.Title = *input.Title
    }
    if input.Completed != nil {
        task.Completed = *input.Completed
    }
    return task, nil
}

func (s *TaskService) DeleteTask(ctx context.Context, id string) error {
    if _, ok := s.tasks[id]; !ok {
        return contract.NotFound("task not found")
    }
    delete(s.tasks, id)
    return nil
}

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

    service := &TaskService{tasks: make(map[string]*Task)}
    c := contract.Register(service)

    // REST API
    app.Mount("/api", rest.Handler(c))

    // OpenAPI spec
    spec := openapi.Generate(c)
    app.Get("/openapi.json", func(ctx *mizu.Ctx) error {
        return ctx.JSON(200, spec)
    })

    app.Listen(":3000")
}

func generateID() string { return "task-1" }

Learn More

Next Steps