@void-snippets/nestjs

Mongoose

forMongooseResource(), createBaseSchema(), and VSDocOf — register a Mongoose-backed resource.

#forMongooseResource — register a Mongoose resource

The one-liner that builds the repository, service, and controller for a Mongoose-backed resource and wires them into your module:

typescript
// contacts.module.ts
import { Module } from '@nestjs/common';
import { forMongooseResource } from '@void-snippets/nestjs/mongoose';
import { ContactSchema } from './contact.schema';

@Module({
  imports: [
    forMongooseResource(
      { name: 'Contact', schema: ContactSchema },
      { softDelete: 'deletedAt' }, // any defineResource option goes here
    ),
  ],
})
export class ContactsModule {}

That's the whole module — no ContactsController, ContactsService, or ContactsRepository classes. The REST base path (/contacts) is derived from the model name, and the id field defaults to "_id", which is what every Mongoose document already has.

#Building the schema — createBaseSchema

Most schemas want the same few things: timestamps, no __v version-key noise in API responses, and sometimes a soft-delete field. createBaseSchema bakes those in so you don't repeat them on every schema:

typescript
// contact.schema.ts
import { createBaseSchema, type VSDocOf } from '@void-snippets/nestjs/mongoose';

export const ContactSchema = createBaseSchema(
  {
    name:  { type: String, required: true },
    email: { type: String, required: true, unique: true },
  },
  { softDelete: true }, // adds an indexed `deletedAt: Date | null` field
);

// The real, honest type of a document from this schema —
// your fields + _id + createdAt + updatedAt + deletedAt.
export type Contact = VSDocOf<typeof ContactSchema, { softDelete: true }>;

VSDocOf exists because a raw Mongoose schema type doesn't know about the fields Mongoose adds at runtime (_id, and createdAt/updatedAt when timestamps: true is set) — without it, TypeScript would tell you those fields don't exist on your own documents.

#Populating references

If your Mongoose resource has references to other documents (like a Contact that references an Organization), tell the resource which paths to populate on list() and get():

typescript
forMongooseResource(
  { name: 'Contact', schema: ContactSchema },
  { populate: 'organizationId' },
  // or several: { populate: ['organizationId', 'ownerId'] }
  // or with a projection: { populate: { path: 'organizationId', select: 'name' } }
);

Populate only runs on read operations that return the entity to the client — never on internal existence checks, and never on create/update/remove responses, so those stay fast.

#Registering an existing definition

If you build the defineResource() config separately (for example, because a hand-written controller subclass also needs to reference it — see Adding Custom Logic), pass the definition as the first argument instead of the inline config:

typescript
import { defineResource } from '@void-snippets/nestjs';
import { forMongooseResource } from '@void-snippets/nestjs/mongoose';

export const contactsResource = defineResource({ name: 'contacts', softDelete: 'deletedAt' });

// elsewhere:
forMongooseResource(contactsResource, { name: 'Contact', schema: ContactSchema });