Models & Schemas
Define data schemas using Zod and interact with MongoDB using the native driver in Thunder.
Thunder uses Zod as its schema definition and validation library. Zod provides a TypeScript-first approach to declaring the shape of your data - from request bodies to database documents - with full static type inference.
For database interaction, Thunder integrates the native MongoDB driver directly, giving you full control over your queries without the overhead of an abstraction layer.
If you prefer to use a different database or ORM/ODM with Thunder, refer to the alternative database guide for integration options.
File Structure
Models live inside the schemas/ directory of your Thunder project. The recommended structure is:
Creating a Model
Define a Zod Schema
Use z.object() to define the shape of your data. It is recommended to separate your input schema (what the user provides) from the full document schema (what gets stored in the database):
import z from "zod";
import { mongodb } from "@/database.ts";
// Input schema - fields accepted from the client
export const userInputSchema = z.object({
name: z.string(),
email: z.string().email(),
password: z.string().min(8),
});
// Full document schema - includes server-managed fields
export const userSchema = userInputSchema.extend({
createdAt: z.date().default(() => new Date()),
updatedAt: z.date().default(() => new Date()),
});Create the Model
Use the native MongoDB driver to bind your schema to a collection:
export const userModel = mongodb.db().collection<
z.infer<typeof userSchema>
>("users");Using the Model
Once a model is defined, import and use it anywhere in your business logic:
import { userModel, userInputSchema } from "@/schemas/user.ts";
// Insert a new user
const { insertedId } = await userModel.insertOne({
name: "Alice",
email: "alice@example.com",
password: "hashed_password",
createdAt: new Date(),
updatedAt: new Date(),
});
// Find a user by email
const user = await userModel.findOne({ email: "alice@example.com" });Import Conventions
Always use the @/ import alias instead of relative paths. Relative imports into core/ or other framework internals are forbidden and will trigger a lint error:
Relative import into "core/" is forbidden. Use "@/core/..." instead.
deno-lint(no-relative-core-imports)Correct:
import { userModel } from "@/schemas/user.ts";Incorrect:
import { userModel } from "../schemas/user.ts"; // ❌ Avoid thisComplete Model Example
import z from "zod";
import { mongodb } from "@/database.ts";
export const envInputSchema = z.object({
key: z.string(),
value: z.string(),
});
export const envSchema = envInputSchema.extend({
createdAt: z.date().default(() => new Date()),
});
export const envModel = mongodb.db().collection<
z.infer<typeof envSchema>
>("envs");