> ## 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.

# REST

> Learn how to expose your Contract service as a RESTful HTTP API

## What is the REST Transport?

The REST transport automatically creates standard HTTP endpoints from your service methods. It uses naming conventions to determine HTTP methods (GET, POST, PUT, DELETE) and paths, so you don't need to write routing code.

REST is the most familiar protocol for web APIs. It's what you use when calling APIs with curl, fetch in JavaScript, or any HTTP client.

## Quick Start

Mount your service with the REST transport:

```go theme={null}
import (
    "github.com/go-mizu/mizu"
    contract "github.com/go-mizu/mizu/contract/v2"
    "github.com/go-mizu/mizu/contract/v2/transport/rest"

    "yourapp/todo"
)

// Your interface is defined in the todo package as todo.API:
// type API interface {
//     Create(ctx context.Context, in *CreateInput) (*Todo, error)
//     List(ctx context.Context) (*ListOutput, error)
//     Get(ctx context.Context, in *GetInput) (*Todo, error)
//     Delete(ctx context.Context, in *DeleteInput) error
// }

// Create your service implementation
impl := todo.NewService()

// Register your service
svc := contract.Register[todo.API](impl,
    contract.WithDefaultResource("todos"),
)

// Create mizu app
app := mizu.New()

// Mount REST endpoints
rest.Mount(app.Router, svc)

// Start server
app.Listen(":8080")
```

Your service now has these endpoints:

| Method | Path          | Handler |
| ------ | ------------- | ------- |
| POST   | `/todos`      | Create  |
| GET    | `/todos`      | List    |
| GET    | `/todos/{id}` | Get     |
| DELETE | `/todos/{id}` | Delete  |

## Method Name to HTTP Mapping

Contract automatically maps method names to HTTP verbs:

| Method Name Starts With           | HTTP Method | Path Pattern           |
| --------------------------------- | ----------- | ---------------------- |
| `Create`, `Add`, `New`            | POST        | `/{resource}`          |
| `List`, `All`, `Search`, `Find*s` | GET         | `/{resource}`          |
| `Get`, `Find`, `Fetch`, `Read`    | GET         | `/{resource}/{id}`     |
| `Update`, `Edit`, `Modify`, `Set` | PUT         | `/{resource}/{id}`     |
| `Delete`, `Remove`                | DELETE      | `/{resource}/{id}`     |
| `Patch`                           | PATCH       | `/{resource}/{id}`     |
| Other names                       | POST        | `/{resource}/{action}` |

### Examples

```go theme={null}
package product

// API defines the contract for product operations
type API interface {
    // POST /products
    Create(ctx context.Context, in *CreateInput) (*Product, error)

    // GET /products
    List(ctx context.Context) (*ListOutput, error)

    // GET /products (with query params)
    Search(ctx context.Context, in *SearchInput) (*ListOutput, error)

    // GET /products/{id}
    Get(ctx context.Context, in *GetInput) (*Product, error)

    // PUT /products/{id}
    Update(ctx context.Context, in *UpdateInput) (*Product, error)

    // DELETE /products/{id}
    Delete(ctx context.Context, in *DeleteInput) error

    // PATCH /products/{id}
    Patch(ctx context.Context, in *PatchInput) (*Product, error)

    // POST /products/archive (custom action)
    Archive(ctx context.Context, in *ArchiveInput) error

    // POST /products/publish (custom action)
    Publish(ctx context.Context, in *PublishInput) (*Product, error)
}
```

## Mount Options

### Mount

Mount all routes directly on the router:

```go theme={null}
rest.Mount(app.Router, svc)
```

### MountAt

Mount routes under a prefix:

```go theme={null}
rest.MountAt(app.Router, "/api/v1", svc)

// Now endpoints are:
// POST /api/v1/todos
// GET /api/v1/todos
// etc.
```

### Getting Routes

Get route definitions without mounting:

```go theme={null}
routes, err := rest.Routes(svc)
if err != nil {
    log.Fatal(err)
}

for _, route := range routes {
    fmt.Printf("%s %s -> %s.%s\n", route.Method, route.Path, route.Resource, route.Handler)
}
```

## Making Requests

### Create (POST)

```bash theme={null}
curl -X POST http://localhost:8080/todos \
  -H "Content-Type: application/json" \
  -d '{"title": "Buy groceries"}'
```

Response:

```json theme={null}
{
  "id": "todo_1",
  "title": "Buy groceries",
  "completed": false
}
```

### List (GET)

```bash theme={null}
curl http://localhost:8080/todos
```

Response:

```json theme={null}
{
  "items": [
    {"id": "todo_1", "title": "Buy groceries", "completed": false},
    {"id": "todo_2", "title": "Walk the dog", "completed": true}
  ],
  "count": 2
}
```

### Get (GET with ID)

```bash theme={null}
curl http://localhost:8080/todos/todo_1
```

Response:

```json theme={null}
{
  "id": "todo_1",
  "title": "Buy groceries",
  "completed": false
}
```

### Update (PUT)

```bash theme={null}
curl -X PUT http://localhost:8080/todos/todo_1 \
  -H "Content-Type: application/json" \
  -d '{"id": "todo_1", "title": "Buy groceries", "completed": true}'
```

Response:

```json theme={null}
{
  "id": "todo_1",
  "title": "Buy groceries",
  "completed": true
}
```

### Delete (DELETE)

```bash theme={null}
curl -X DELETE http://localhost:8080/todos/todo_1
```

Response: HTTP 204 No Content (empty body)

## Path Parameters

For methods that need an ID from the URL path, use the `path` tag:

```go theme={null}
type GetInput struct {
    ID string `json:"id" path:"id"`  // Extracted from {id} in the path
}

type UpdateInput struct {
    ID        string `json:"id" path:"id"`
    Title     string `json:"title"`
    Completed bool   `json:"completed"`
}
```

### Multiple Path Parameters

For nested resources:

```go theme={null}
// Path: /users/{userId}/posts/{id}
type GetPostInput struct {
    UserID string `json:"userId" path:"userId"`
    PostID string `json:"postId" path:"id"`
}
```

Register with custom HTTP binding:

```go theme={null}
svc := contract.Register[PostAPI](impl,
    contract.WithMethodHTTP("GetPost", "GET", "/users/{userId}/posts/{id}"),
)
```

## Query Parameters

By default, input comes from the request body. For GET requests, you may want query parameters:

```go theme={null}
type ListInput struct {
    Limit  int    `json:"limit" query:"limit"`
    Offset int    `json:"offset" query:"offset"`
    Status string `json:"status" query:"status"`
}
```

Request:

```bash theme={null}
curl "http://localhost:8080/todos?limit=10&offset=0&status=completed"
```

## Custom HTTP Bindings

Override the automatic HTTP mapping with `WithMethodHTTP`:

```go theme={null}
svc := contract.Register[TodoAPI](impl,
    contract.WithDefaultResource("todos"),

    // Custom path for archive action
    contract.WithMethodHTTP("Archive", "POST", "/todos/{id}/archive"),

    // API versioning
    contract.WithMethodHTTP("Create", "POST", "/v2/todos"),
)
```

Or set multiple bindings at once:

```go theme={null}
svc := contract.Register[TodoAPI](impl,
    contract.WithHTTP(map[string]contract.HTTPBinding{
        "Create": {Method: "POST", Path: "/v2/todos"},
        "Get":    {Method: "GET", Path: "/v2/todos/{id}"},
        "List":   {Method: "GET", Path: "/v2/todos"},
    }),
)
```

## Complete Example

This example shows a complete REST service using the recommended package-based organization:

```go theme={null}
// todo/types.go
package todo

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

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

type GetInput struct {
    ID string `json:"id" path:"id"`
}

type UpdateInput struct {
    ID        string `json:"id" path:"id"`
    Title     string `json:"title"`
    Completed bool   `json:"completed"`
}

type DeleteInput struct {
    ID string `json:"id" path:"id"`
}

type ListOutput struct {
    Items []*Todo `json:"items"`
    Count int     `json:"count"`
}
```

```go theme={null}
// todo/api.go
package todo

import "context"

// API defines the contract for todo operations
type API interface {
    Create(ctx context.Context, in *CreateInput) (*Todo, error)
    List(ctx context.Context) (*ListOutput, error)
    Get(ctx context.Context, in *GetInput) (*Todo, error)
    Update(ctx context.Context, in *UpdateInput) (*Todo, error)
    Delete(ctx context.Context, in *DeleteInput) error
}
```

```go theme={null}
// todo/service.go
package todo

import (
    "context"
    "fmt"
    "sync"

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

// Service implements todo.API
type Service struct {
    mu     sync.RWMutex
    todos  map[string]*Todo
    nextID int
}

// Compile-time check
var _ API = (*Service)(nil)

func NewService() *Service {
    return &Service{todos: make(map[string]*Todo)}
}

func (s *Service) Create(ctx context.Context, in *CreateInput) (*Todo, error) {
    if in.Title == "" {
        return nil, contract.ErrInvalidArgument("title is required")
    }

    s.mu.Lock()
    defer s.mu.Unlock()

    s.nextID++
    todo := &Todo{
        ID:    fmt.Sprintf("todo_%d", s.nextID),
        Title: in.Title,
    }
    s.todos[todo.ID] = todo
    return todo, nil
}

func (s *Service) List(ctx context.Context) (*ListOutput, error) {
    s.mu.RLock()
    defer s.mu.RUnlock()

    items := make([]*Todo, 0, len(s.todos))
    for _, t := range s.todos {
        items = append(items, t)
    }
    return &ListOutput{Items: items, Count: len(items)}, nil
}

func (s *Service) Get(ctx context.Context, in *GetInput) (*Todo, error) {
    s.mu.RLock()
    defer s.mu.RUnlock()

    todo, ok := s.todos[in.ID]
    if !ok {
        return nil, contract.ErrNotFound("todo not found")
    }
    return todo, nil
}

func (s *Service) Update(ctx context.Context, in *UpdateInput) (*Todo, error) {
    s.mu.Lock()
    defer s.mu.Unlock()

    todo, ok := s.todos[in.ID]
    if !ok {
        return nil, contract.ErrNotFound("todo not found")
    }
    todo.Title = in.Title
    todo.Completed = in.Completed
    return todo, nil
}

func (s *Service) Delete(ctx context.Context, in *DeleteInput) error {
    s.mu.Lock()
    defer s.mu.Unlock()

    if _, ok := s.todos[in.ID]; !ok {
        return contract.ErrNotFound("todo not found")
    }
    delete(s.todos, in.ID)
    return nil
}
```

```go theme={null}
// main.go
package main

import (
    "fmt"

    "github.com/go-mizu/mizu"
    contract "github.com/go-mizu/mizu/contract/v2"
    "github.com/go-mizu/mizu/contract/v2/transport/rest"

    "yourapp/todo"
)

func main() {
    impl := todo.NewService()

    svc := contract.Register[todo.API](impl,
        contract.WithName("Todo"),
        contract.WithDefaultResource("todos"),
    )

    app := mizu.New()

    // Mount REST
    rest.Mount(app.Router, svc)

    // Print routes
    fmt.Println("REST Endpoints:")
    fmt.Println("  POST   /todos      - Create a todo")
    fmt.Println("  GET    /todos      - List all todos")
    fmt.Println("  GET    /todos/{id} - Get a todo")
    fmt.Println("  PUT    /todos/{id} - Update a todo")
    fmt.Println("  DELETE /todos/{id} - Delete a todo")

    app.Listen(":8080")
}
```

## Error Responses

Use Contract errors for proper HTTP status codes:

```go theme={null}
// HTTP 404
return nil, contract.ErrNotFound("todo not found")

// HTTP 400
return nil, contract.ErrInvalidArgument("title is required")

// HTTP 401
return nil, contract.ErrUnauthenticated("please log in")

// HTTP 403
return nil, contract.ErrPermissionDenied("admin access required")

// HTTP 500
return nil, contract.ErrInternal("database error")
```

Error response format:

```json theme={null}
{
  "error": {
    "code": "NOT_FOUND",
    "message": "todo not found"
  }
}
```

## Multiple Services

Mount multiple services on the same router:

```go theme={null}
todoSvc := contract.Register[TodoAPI](todoImpl, contract.WithDefaultResource("todos"))
userSvc := contract.Register[UserAPI](userImpl, contract.WithDefaultResource("users"))

app := mizu.New()

rest.Mount(app.Router, todoSvc)  // /todos/*
rest.Mount(app.Router, userSvc)  // /users/*

app.Listen(":8080")
```

## Using with Middleware

Apply middleware to your routes:

```go theme={null}
app := mizu.New()

// Global middleware
app.Use(loggingMiddleware)
app.Use(corsMiddleware)

// Auth middleware for specific routes
app.Use(authMiddleware, "/todos/*")

// Mount REST
rest.Mount(app.Router, svc)

app.Listen(":8080")
```

## OpenAPI Documentation

Generate OpenAPI spec from your service:

```go theme={null}
spec, err := rest.OpenAPI(svc.Descriptor())
if err != nil {
    log.Fatal(err)
}

// Serve the spec
app.Get("/openapi.json", func(c *mizu.Ctx) error {
    return c.JSON(200, spec)
})
```

See the [OpenAPI documentation](/contract/openapi) for more details.

## Common Questions

### How do I change the path for a method?

Use `WithMethodHTTP`:

```go theme={null}
contract.WithMethodHTTP("Archive", "POST", "/todos/{id}/archive")
```

### How do I add query parameters?

Use the `query` tag on input struct fields:

```go theme={null}
type ListInput struct {
    Page int `json:"page" query:"page"`
}
```

### Can I use different content types?

By default, REST uses `application/json`. For other content types, use standard mizu handlers instead of Contract.

### How do I handle file uploads?

File uploads aren't currently supported through Contract. Use standard mizu file handling for upload endpoints.

### How do I add authentication?

Use mizu middleware to add authentication before requests reach your service methods. See [Mizu Middleware](/middlewares) documentation.

## What's Next?

* **[JSON-RPC Transport](/contract/jsonrpc)** - For batching and RPC-style APIs
* **[MCP Transport](/contract/mcp)** - Connect your API to AI assistants
* **[OpenAPI](/contract/openapi)** - Generate API documentation
* **[Error Handling](/contract/errors)** - Proper error responses
