A Small CRUD API
This recipe is deliberately small but complete. It uses a group for the resource prefix, a typed request body, brace parameters, and Zinc errors for missing records.
mkdir zinc-widgetscd zinc-widgetsgo mod init example.com/zinc-widgetsgo get github.com/0mjs/zincApplication
Section titled “Application”package main
import ( "log" "strconv" "sync"
"github.com/0mjs/zinc")
type Widget struct { ID int `json:"id"` Name string `json:"name"`}
type WidgetInput struct { Name string `json:"name"`}
type widgetStore struct { mu sync.RWMutex nextID int items map[int]Widget}
func newWidgetStore() *widgetStore { return &widgetStore{nextID: 1, items: make(map[int]Widget)}}
func main() { store := newWidgetStore() app := zinc.New() widgets := app.Group("/widgets")
widgets.Get("/", func(c *zinc.Context) error { store.mu.RLock() defer store.mu.RUnlock()
items := make([]Widget, 0, len(store.items)) for _, widget := range store.items { items = append(items, widget) } return c.JSON(items) })
widgets.Post("/", func(c *zinc.Context) error { var input WidgetInput if err := c.Bind().JSON(&input); err != nil { return err } if input.Name == "" { return zinc.ErrBadRequest.WithMessage("name is required") }
store.mu.Lock() widget := Widget{ID: store.nextID, Name: input.Name} store.items[widget.ID] = widget store.nextID++ store.mu.Unlock()
return c.Status(zinc.StatusCreated).JSON(widget) })
widgets.Get("/{id}", func(c *zinc.Context) error { id, err := strconv.Atoi(c.Param("id")) if err != nil { return zinc.ErrBadRequest.WithMessage("id must be an integer") }
store.mu.RLock() widget, ok := store.items[id] store.mu.RUnlock() if !ok { return zinc.ErrNotFound.WithMessage("widget not found") } return c.JSON(widget) })
widgets.Put("/{id}", func(c *zinc.Context) error { id, err := strconv.Atoi(c.Param("id")) if err != nil { return zinc.ErrBadRequest.WithMessage("id must be an integer") }
var input WidgetInput if err := c.Bind().JSON(&input); err != nil { return err } if input.Name == "" { return zinc.ErrBadRequest.WithMessage("name is required") }
store.mu.Lock() if _, ok := store.items[id]; !ok { store.mu.Unlock() return zinc.ErrNotFound.WithMessage("widget not found") } widget := Widget{ID: id, Name: input.Name} store.items[id] = widget store.mu.Unlock()
return c.JSON(widget) })
widgets.Delete("/{id}", func(c *zinc.Context) error { id, err := strconv.Atoi(c.Param("id")) if err != nil { return zinc.ErrBadRequest.WithMessage("id must be an integer") }
store.mu.Lock() if _, ok := store.items[id]; !ok { store.mu.Unlock() return zinc.ErrNotFound.WithMessage("widget not found") } delete(store.items, id) store.mu.Unlock()
return c.NoContent() })
log.Fatal(app.Listen(":8080"))}Try it
Section titled “Try it”curl -i http://localhost:8080/widgets \ -H 'Content-Type: application/json' \ --data '{"name":"bracket"}'
curl http://localhost:8080/widgets/1
curl -i -X PUT http://localhost:8080/widgets/1 \ -H 'Content-Type: application/json' \ --data '{"name":"hinge"}'
curl -i -X DELETE http://localhost:8080/widgets/1The in-memory store is safe for concurrent requests but intentionally does not survive restarts. Continue with the SQLite CRUD API when you want persistence.
