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.
1. Create a Project
Section titled “1. Create a Project”jazzy new todo_appcd todo_appnimble install -yThe 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.
2. Configure the Database
Section titled “2. Configure the Database”For SQLite, the generated .env is already enough:
APP_ENV=developmentDEV_UI_ENABLED=trueDB_CONNECTION=sqliteDB_DATABASE=database.sqliteJWT_SECRET=replace-with-a-random-secret-of-at-least-32-charactersFor PostgreSQL, replace the SQLite settings with a URL:
DB_CONNECTION=postgresDATABASE_URL=postgresql://jazzy:secret@127.0.0.1:5432/todo_appDB_POOL_MIN=1DB_POOL_MAX=1Jazzy reads .env automatically. Do not add connectDB() to a new project.
3. Apply the First Migration
Section titled “3. Apply the First Migration”The generated src/migrations/m00000000000000_create_todos.nim creates the
Todo table. Apply it once before starting the app:
jazzy migrateThe migration selects SQLite or PostgreSQL from DB_CONNECTION. To evolve the
schema later, create another migration with jazzy make:migration <name>.
4. Create a Controller
Section titled “4. Create a Controller”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.
5. Register Routes
Section titled “5. Register Routes”Create router.nim:
import jazzyimport 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)6. Start the Application
Section titled “6. Start the Application”Connect the pieces in app.nim:
import jazzyimport router
proc main() = registerRoutes() Jazzy.serve(8080)
when isMainModule: main()Run the application:
nimble c -r src/app.nimTry it:
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.
Next Steps
Section titled “Next Steps”- Learn the complete Database API.
- Learn how Migrations and the optional ORM work.
- Add validation rules to controllers.
- Learn how routing and middleware work.
- Use the local-only Dev UI while developing.