Skip to main content

SQLite CRUD API

This recipe is the simplest "real API" example in the cookbook:

  • SQLite storage
  • request binding
  • route params
  • JSON responses
  • create, list, update, and delete flows

Setup

go mod init zinc-sqlite
go get github.com/0mjs/zinc
go get github.com/mattn/go-sqlite3

Application

package main

import (
"database/sql"
"log"

_ "github.com/mattn/go-sqlite3"
"github.com/0mjs/zinc"
)

type Todo struct {
ID int `json:"id"`
Title string `json:"title"`
Done bool `json:"done"`
}

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

func main() {
db, err := sql.Open("sqlite3", "file:todo.db?_fk=1")
if err != nil {
log.Fatal(err)
}
defer db.Close()

_, err = db.Exec(`
create table if not exists todos (
id integer primary key autoincrement,
title text not null,
done boolean not null default 0
)
`)
if err != nil {
log.Fatal(err)
}

app := zinc.New()

app.Get("/todos", func(c *zinc.Context) error {
rows, err := db.Query("select id, title, done from todos order by id asc")
if err != nil {
return err
}
defer rows.Close()

todos := make([]Todo, 0)
for rows.Next() {
var t Todo
if err := rows.Scan(&t.ID, &t.Title, &t.Done); err != nil {
return err
}
todos = append(todos, t)
}

return c.JSON(todos)
})

app.Post("/todos", func(c *zinc.Context) error {
var input CreateTodoInput
if err := c.Bind().JSON(&input); err != nil {
return err
}
if input.Title == "" {
return zinc.ErrBadRequest.WithMessage("title is required")
}

result, err := db.Exec("insert into todos(title, done) values(?, 0)", input.Title)
if err != nil {
return err
}

id, _ := result.LastInsertId()
return c.Status(zinc.StatusCreated).JSON(Todo{
ID: int(id),
Title: input.Title,
Done: false,
})
})

app.Patch("/todos/:id/done", func(c *zinc.Context) error {
id := c.Param("id")
if id == "" {
return zinc.ErrBadRequest.WithMessage("id is required")
}

_, err := db.Exec("update todos set done = 1 where id = ?", id)
if err != nil {
return err
}

return c.NoContent()
})

app.Delete("/todos/:id", func(c *zinc.Context) error {
id := c.Param("id")
if id == "" {
return zinc.ErrBadRequest.WithMessage("id is required")
}

_, err := db.Exec("delete from todos where id = ?", id)
if err != nil {
return err
}

return c.NoContent()
})

app.Listen()
}

Try it

curl -X POST http://localhost:8080/todos \
-H "Content-Type: application/json" \
-d '{"title":"ship docs"}'

curl http://localhost:8080/todos
curl -X PATCH http://localhost:8080/todos/1/done
curl -X DELETE http://localhost:8080/todos/1

Why this recipe matters

This is the most direct example of Zinc as an API framework:

  • bind request input
  • validate what you need
  • use route params
  • return JSON and status codes
  • keep the handler flow explicit