# New Model

> Scaffold a new Mongoose model and its CRUD service for the WealthWise API. Triggers when asked to "create a model", "add a Mongoose schema", or add the data layer for a new entity without a full endpoint. Does NOT scaffold routes, controllers, or frontend code. Use $api-endpoint for the full stack.

- Skill: `hoangsonww/new-model` (Agent Skill)
- Install (CLI): `npx skillmds@latest add hoangsonww/new-model`
- Raw SKILL.md: https://api.skillmd.com/api/skills/hoangsonww/new-model/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Web & Frontend
- Author: hoangsonww (https://skillmd.com/u/hoangsonww)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/hoangsonww/new-model

---


Scaffold a new Mongoose model with its full CRUD service.

The entity name is provided in the task prompt.

## Files to create

### Model — `apps/api/src/models/<entity>.model.ts`

```typescript
import { Schema, model, Document, Types } from 'mongoose';

export interface I<Entity> extends Document {
  userId: Types.ObjectId;
  // entity-specific fields
  createdAt: Date;
  updatedAt: Date;
}

const <entity>Schema = new Schema<I<Entity>>(
  {
    userId: {
      type: Schema.Types.ObjectId,
      ref: 'User',
      required: true,
      index: true,
    },
    // entity fields
  },
  {
    timestamps: true,    // handles createdAt/updatedAt automatically
    toJSON: { virtuals: true },
    toObject: { virtuals: true },
  }
);

// All indexes defined here — never outside the schema file
<entity>Schema.index({ userId: 1, createdAt: -1 });

export const <Entity>Model = model<I<Entity>>('<Entity>', <entity>Schema);
```

### Service — `apps/api/src/services/<entity>.service.ts`

Five methods, fully typed:

1. `get<Entity>s(userId: string): Promise<I<Entity>[]>`
   - Filter: `{ userId: new Types.ObjectId(userId) }`

2. `get<Entity>ById(id: string, userId: string): Promise<I<Entity>>`
   - Throw `ApiError.notFound('<Entity> not found')` if not found — never return null

3. `create<Entity>(userId: string, data: Create<Entity>Input): Promise<I<Entity>>`
   - Attach `userId` to the document before saving

4. `update<Entity>(id: string, userId: string, data: Update<Entity>Input): Promise<I<Entity>>`
   - Filter by both `_id` and `userId` — throw `ApiError.notFound` if missing
   - Use `{ new: true }` in `findOneAndUpdate`

5. `delete<Entity>(id: string, userId: string): Promise<void>`
   - Filter by both `_id` and `userId` — throw `ApiError.notFound` if missing

## Rules

- Import `ApiError` from `utils/api-error.ts` — never `throw new Error(...)`
- Every query includes `userId` as a filter — no cross-user access
- Use `new Types.ObjectId(userId)` when building ObjectId filters
- Never expose raw Mongoose errors — catch and rethrow as `ApiError.internal(...)`
- Named exports only. No `any`.

## Verification

```bash
npx turbo lint --filter=@wealthwise/api
```

List all created file paths and the five service method signatures.

