Skip to content

Getting Started

This guide builds a small Todo API. The same application works with SQLite for local development or PostgreSQL in production.

YOUR FIRST JAZZY API

Six small steps. One working Todo API.

Follow the path in order, or jump straight to the part you need. The application code stays the same for local SQLite and production PostgreSQL.

~10 minutesSQLite or PostgreSQLawait-first
Terminal window
jazzy new todo_app
cd todo_app
nimble install -y

The generated project includes app.nim, router.nim, a first migration, a controller, and .env. There is no migration runner file to manage: the CLI generates its ignored runner in .jazzy/ when needed.

For SQLite, the generated .env is already enough:

APP_ENV=development
DEV_UI_ENABLED=true
DB_CONNECTION=sqlite
DB_DATABASE=database.sqlite
JWT_SECRET=replace-with-a-random-secret-of-at-least-32-characters

For PostgreSQL, replace the SQLite settings with a URL:

DB_CONNECTION=postgres
DATABASE_URL=postgresql://jazzy:secret@127.0.0.1:5432/todo_app
DB_POOL_MIN=1
DB_POOL_MAX=1

Jazzy reads .env automatically. Do not add connectDB() to a new project.

The generated src/migrations/m00000000000000_create_todos.nim creates the Todo table. Apply it once before starting the app:

Terminal window
jazzy migrate

The migration selects SQLite or PostgreSQL from DB_CONNECTION. To evolve the schema later, create another migration with jazzy make:migration <name>.

Create controllers/todo_controller.nim:

import jazzy
proc list*(ctx: Context) {.async.} =
let todos = await DB.table("todos")
.orderBy("id", "DESC")
.get()
ctx.json(todos)
proc create*(ctx: Context) {.async.} =
let data = ctx.validate(%*{
"title": "required|min:3"
})
let todo = await DB.table("todos")
.returning("id", "title", "completed", "created_at")
.insert(%*{
"title": data["title"].getStr,
"completed": false
})
ctx.status(201).json(todo)
proc complete*(ctx: Context) {.async.} =
let changed = await DB.table("todos")
.where("id", ctx.param("id"))
.update(%*{"completed": true})
if changed == 0:
ctx.status(404).json(%*{"error": "Todo not found"})
return
ctx.json(%*{"status": "completed"})

Notice that each database operation uses await. The route parameter in ctx.param("id") is a string; Jazzy automatically handles the PostgreSQL numeric cast when the column is a normal numeric ID.

Create router.nim:

import jazzy
import controllers/todo_controller
proc registerRoutes*() =
Route.groupPath("/todos"):
Route.get("/", todo_controller.list)
Route.post("/", todo_controller.create)
Route.patch("/:id/complete", todo_controller.complete)

Connect the pieces in app.nim:

import jazzy
import router
proc main() =
registerRoutes()
Jazzy.serve(8080)
when isMainModule:
main()

Run the application:

Terminal window
nimble c -r src/app.nim

Try it:

Terminal window
curl -X POST http://localhost:8080/todos/ ^
-H "Content-Type: application/json" ^
-d "{\"title\": \"Learn Jazzy\"}"

Then open http://localhost:8080/todos/ in a browser or HTTP client.