Use this file to discover all available pages before exploring further.
In this tutorial, youβll build a todo list API with full CRUD operations. CRUD stands for Create, Read, Update, Delete - the four basic operations for managing data. This is the foundation of almost every web application, and youβll learn the patterns used in production Mizu APIs.
package todosimport ( "sync" "github.com/go-mizu/mizu")// Todo represents a todo item.type Todo struct { ID string `json:"id"` Title string `json:"title"` Completed bool `json:"completed"`}// Store is an in-memory todo store.type Store struct { mu sync.RWMutex todos map[string]*Todo nextID int}// NewStore creates a new todo store.func NewStore() *Store { return &Store{ todos: make(map[string]*Todo), }}
This sets up our data types and a simple in-memory store.
// List returns a handler that lists all todos.func List(store *Store) mizu.Handler { return func(c *mizu.Ctx) error { store.mu.RLock() defer store.mu.RUnlock() todos := make([]*Todo, 0, len(store.todos)) for _, t := range store.todos { todos = append(todos, t) } return c.JSON(200, todos) }}
// Get returns a handler that gets a todo by ID.func Get(store *Store) mizu.Handler { return func(c *mizu.Ctx) error { id := c.Param("id") store.mu.RLock() defer store.mu.RUnlock() todo, ok := store.todos[id] if !ok { return c.JSON(404, map[string]string{ "error": "todo not found", }) } return c.JSON(200, todo) }}
package todosimport ( "fmt" "sync" "github.com/go-mizu/mizu")// Todo represents a todo item.type Todo struct { ID string `json:"id"` Title string `json:"title"` Completed bool `json:"completed"`}// Store is an in-memory todo store.type Store struct { mu sync.RWMutex todos map[string]*Todo nextID int}// NewStore creates a new todo store.func NewStore() *Store { return &Store{ todos: make(map[string]*Todo), }}// CreateInput is the input for creating a todo.type CreateInput struct { Title string `json:"title"`}// UpdateInput is the input for updating a todo.type UpdateInput struct { Title *string `json:"title"` Completed *bool `json:"completed"`}// List returns a handler that lists all todos.func List(store *Store) mizu.Handler { return func(c *mizu.Ctx) error { store.mu.RLock() defer store.mu.RUnlock() todos := make([]*Todo, 0, len(store.todos)) for _, t := range store.todos { todos = append(todos, t) } return c.JSON(200, todos) }}// Create returns a handler that creates a todo.func Create(store *Store) mizu.Handler { return func(c *mizu.Ctx) error { var input CreateInput if err := c.Bind(&input); err != nil { return err } if input.Title == "" { return c.JSON(400, map[string]string{"error": "title is required"}) } store.mu.Lock() defer store.mu.Unlock() store.nextID++ todo := &Todo{ ID: fmt.Sprintf("%d", store.nextID), Title: input.Title, Completed: false, } store.todos[todo.ID] = todo return c.JSON(201, todo) }}// Get returns a handler that gets a todo by ID.func Get(store *Store) mizu.Handler { return func(c *mizu.Ctx) error { id := c.Param("id") store.mu.RLock() defer store.mu.RUnlock() todo, ok := store.todos[id] if !ok { return c.JSON(404, map[string]string{"error": "todo not found"}) } return c.JSON(200, todo) }}// Update returns a handler that updates a todo.func Update(store *Store) mizu.Handler { return func(c *mizu.Ctx) error { id := c.Param("id") var input UpdateInput if err := c.Bind(&input); err != nil { return err } store.mu.Lock() defer store.mu.Unlock() todo, ok := store.todos[id] if !ok { return c.JSON(404, map[string]string{"error": "todo not found"}) } if input.Title != nil { todo.Title = *input.Title } if input.Completed != nil { todo.Completed = *input.Completed } return c.JSON(200, todo) }}// Delete returns a handler that deletes a todo.func Delete(store *Store) mizu.Handler { return func(c *mizu.Ctx) error { id := c.Param("id") store.mu.Lock() defer store.mu.Unlock() if _, ok := store.todos[id]; !ok { return c.JSON(404, map[string]string{"error": "todo not found"}) } delete(store.todos, id) return c.JSON(200, map[string]string{"message": "deleted"}) }}