npm.io
0.2.3 • Published yesterday

@jream/zodgoose

Licence
MIT
Version
0.2.3
Deps
0
Size
119 kB
Vulns
0
Weekly
0
zodgoose

Zodgoose — Create Mongoose Schemas with Zod

Originally created by Andrew Kazakov (mongoose-zod). Maintained by Jesse Boyer.


Install

npm install @jream/zodgoose
pnpm add @jream/zodgoose
yarn add @jream/zodgoose
bun add @jream/zodgoose

Peer dependencies: mongoose@>=7.0.0, zod@>=4.0.0

Quick Start

import mongoose from 'mongoose';
import { z } from 'zod';
import { toMongooseSchema, zodgooseCustomType, genTimestampsSchema } from '@jream/zodgoose';

// Define a reusable address schema
const addressSchema = z.object({
  street: z.string(),
  city: z.string(),
  zip: z.string(),
  country: z.string().default('US'),
});

// Define the user schema with nested fields, timestamps, and custom types
const userSchema = z.object({
  name: z.string().min(1),
  email: z.string().email(),
  password: z.string().min(8),
  uid: z.string().uuid(),
  age: z.number().int().positive().optional(),
  bio: z.string().max(500).optional(),
  role: z.enum(['admin', 'user', 'moderator']).default('user'),
  address: addressSchema,
  tags: z.array(z.string()).optional(),
  avatar: zodgooseCustomType('Buffer').optional(),
  owner: zodgooseCustomType('ObjectId'),
})
  .merge(genTimestampsSchema('createdAt', 'updatedAt'))
  .mongoose({
    schemaOptions: { collection: 'users' },
    typeOptions: {
      email: { unique: true, index: true },
      name: { required: true },
    },
  });

const UserSchema = toMongooseSchema(userSchema);
const User = mongoose.model('User', UserSchema);

const user = new User({
  name: 'Alice',
  email: 'alice@example.com',
  password: 'securepass123',
  uid: '550e8400-e29b-41d4-a716-446655440000',
  address: { street: '123 Main St', city: 'Springfield', zip: '12345', country: 'US' },
  tags: ['typescript', 'mongodb'],
  owner: new mongoose.Types.ObjectId(),
});
console.log(user.name);

Schema Options

Use .mongoose() for Mongoose-specific options like schemaOptions (collection, timestamps, etc.) and typeOptions (unique, index, required, etc.):

const schema = z.object({
  email: z.string().email(),
  name: z.string().min(1),
}).mongoose({
  schemaOptions: {
    collection: 'users',
    optimisticConcurrency: true,
  },
  typeOptions: {
    email: { unique: true, index: true },
    name: { required: true, zgValidate: (v: string) => v.length > 0 },
  },
});

Special Types

import { zodgooseCustomType, genTimestampsSchema } from '@jream/zodgoose';

// Buffer, ObjectId, Decimal128, etc.
z.object({
  avatar: zodgooseCustomType('Buffer'),
  owner: zodgooseCustomType('ObjectId'),
}).mongoose();

// Timestamps
z.object({ name: z.string() })
  .merge(genTimestampsSchema('createdAt', 'updatedAt'))
  .mongoose();

Custom Type Registry

Register custom Mongoose types for use with zodgooseCustomType():

import { registerCustomType, isRegisteredCustomType, listRegisteredCustomTypes } from '@jream/zodgoose';

registerCustomType('MyType', () => mongoose.Schema.Types.Mixed);
console.log(isRegisteredCustomType('MyType')); // true
console.log(listRegisteredCustomTypes()); // ['Buffer', 'ObjectId', 'Decimal128', 'UUID', 'BigInt', 'Double', 'Int32', 'MyType']

Discriminator Support

Use the discriminator() helper for Mongoose schema inheritance:

import { discriminator, getDiscriminators } from '@jream/zodgoose';

const base = z.object({ name: z.string() }).mongoose({ schemaOptions: { discriminatorKey: 'kind' } });
const child = z.object({ name: z.string(), age: z.number() }).mongoose();

discriminator(base, 'Adult', child);

const Schema = toMongooseSchema(base);
// Schema now has the 'Adult' discriminator registered

Type Inference

const userZodSchema = z.object({
  email: z.string().email(),
  name: z.string(),
}).mongoose();

type UserType = z.infer<typeof userZodSchema>;
// UserType === { email: string; name: string }

const UserSchema = toMongooseSchema(userZodSchema);
const User = mongoose.model('User', UserSchema);

const user = new User({ email: 'test@example.com', name: 'Test' });

Safety Defaults

Zodgoose removes problematic Mongoose defaults:

Issue Mongoose Zodgoose
Array defaults [] undefined
Root id virtual Added Not added
Empty object removal Enabled Disabled
Sub-schema _id Added Not added
Number/string casting Enabled Disabled
Extraneous fields Silent strip Throws error

Plugin Support

If installed, these plugins auto-enable on every schema:

  • mongoose-lean-virtuals
  • mongoose-lean-defaults
  • mongoose-lean-getters
// .lean() automatically includes plugin options
const user = await User.findOne({ _id }).lean();
// Equivalent to:
const user = await User.findOne({ _id }).lean({
  virtuals: true,
  defaults: true,
  getters: true,
  versionKey: false,
});

Disable per-schema:

toMongooseSchema(schema, { disablePlugins: { leanVirtuals: true } });

API

toMongooseSchema(zodSchema, options?)

Converts a Zod schema to Mongoose schema.

const schema = toMongooseSchema(userZodSchema, {
  unknownKeys: 'strip', // 'throw' | 'strip' | 'strip-unless-overridden'
  disablePlugins: { leanVirtuals: true },
});
.mongoose(options?)

Adds Mongoose options to a Zod schema.

z.object({ name: z.string() }).mongoose({
  schemaOptions: { collection: 'users' },
  typeOptions: { name: { required: true } },
});
zodgooseCustomType(typeName)

Use special Mongoose types.

z.object({
  avatar: zodgooseCustomType('Buffer'),
  ref: zodgooseCustomType('ObjectId'),
});
genTimestampsSchema(createdAt, updatedAt)

Generate timestamp fields.

z.object({ name: z.string() })
  .merge(genTimestampsSchema('createdAt', 'updatedAt'))
  .mongoose();
zgValidate / zgRequired

Type-safe alternatives to Mongoose's validate and required.

z.string().mongooseTypeOptions({
  validate: zgValidate((value) => value.length > 0),
});
discriminator(baseSchema, name, childSchema, options?)

Attach a Mongoose discriminator to a Zodgoose schema.

const base = z.object({ name: z.string() }).mongoose({ schemaOptions: { discriminatorKey: 'kind' } });
const child = z.object({ name: z.string(), age: z.number() }).mongoose();
discriminator(base, 'Adult', child);
getDiscriminators(schema)

Retrieve discriminator entries from a Zodgoose schema.

const discs = getDiscriminators(baseSchema);
registerCustomType(name, getter)

Register a custom Mongoose type for use with zodgooseCustomType().

registerCustomType('MyType', () => mongoose.Schema.Types.Mixed);
isRegisteredCustomType(name)

Check if a custom type is registered.

listRegisteredCustomTypes()

List all registered custom type names.

Zod 4.x Type Support

Zodgoose supports all Zod 4.x types:

Zod Type Mongoose Type Notes
z.string() String
z.number() Number
z.boolean() Boolean
z.date() Date
z.object({...}) Schema Nested schemas
z.array(z.string()) [String]
z.enum(['a', 'b']) String enum
z.literal(x) Inferred type
z.union([...]) Mixed
z.intersection(a, b) Mixed
z.discriminatedUnion(...) Mixed
z.record(z.number()) Mixed
z.tuple([...]) Mixed
z.any() / z.unknown() Mixed
z.nan() / z.null() Mixed
z.never() Mixed
z.map(z.number(), z.string()) Map
z.set(z.string()) Mixed No Mongoose equivalent
z.symbol() Mixed
z.xor(a, b) Mixed
z.file() Mixed
z.email() String
z.uuid() String
z.ulid() String
z.nanoid() String
z.cuid() / z.cuid2() String
z.url() String
z.emoji() String
z.ip() / z.ipv4() / z.ipv6() String
z.mac() String
z.cidr() String
z.base64() / z.base64url() String
z.e164() String
z.jwt() String
z.isoDateTime() / z.isoDate() / z.isoTime() String
z.isoDuration() String
z.guid() String
z.int() / z.float64() Number
z.int64() / z.uint64() Number
z.bigint() Number
z.stringbool() String Codec unwrap
z.string().catch(x) Inner type Unwraps to string
z.string().prefault() Inner type Unwraps to string
z.string().optional().nonOptional() Inner type Unwraps to string
z.readonly() Inner type Unwraps
z.lazy(() => ...) Inner type Cycle-safe
z.pipe(a, b) Inner type Unwraps through pipe
z.transform(...) Error Not supported
z.object({...}).mongoose() Schema With .mongoose() metadata

License

MIT License - see LICENSE.md


JREAM

Keywords