The Todo Model
Define the Todo schema and MongoDB model with Zod.
Every Thunder model is a Zod schema bound to a MongoDB collection. We'll separate the input schema ($todoInput - what the client sends) from the document schema ($todo - what's stored), following the Models & Schemas convention.
Define the Schema
import z from "zod";
import { mongodb } from "@/database.ts";
import { $objectId } from "@/core/utils/createCRUD.ts";
// Fields accepted from the client
export const $todoInput = z.object({
title: z.string().min(1).max(200),
completed: z.boolean().default(false),
});
// Full document, including server-managed fields
export const $todo = $todoInput.extend({
_id: $objectId.optional(),
createdAt: z.date().default(() => new Date()),
});
// Bind the schema to the "todos" collection
export const todoModel = mongodb.db().collection<
z.infer<typeof $todo>
>("todos");Why Two Schemas?
| Schema | Used for |
|---|---|
$todoInput | Validating request bodies on create/update. Clients never set _id or createdAt. |
$todo | Validating full documents and response shapes; binds the collection type. |
When inserting, validate with $todo.strictParse(...) so defaults like completed and createdAt are applied automatically. See strictParse.
(Optional) Add an Index
Todos are often listed newest-first, so add an index for createdAt. Per Thunder's serverless-first model, indexes are created via a script:
import { todoModel } from "@/schemas/todo.ts";
await todoModel.createIndex({ createdAt: -1 });
console.log("✓ Indexes created");deno run -A scripts/createIndexes.tsWith the model in place, let's build the API.