# API Endpoint

> Scaffold a complete new API endpoint for the WealthWise Express API. Triggers when asked to "add an endpoint", "create a route", "build an API for <entity>", or scaffold any new REST resource end-to-end. Does not trigger for frontend-only tasks.

- Skill: `hoangsonww/api-endpoint` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add hoangsonww/api-endpoint`
- Raw SKILL.md: https://api.skillmd.com/api/skills/hoangsonww/api-endpoint/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/api-endpoint

---


Scaffold a complete new API endpoint for the WealthWise API following all project conventions.

The entity/resource name is provided in the task prompt.

## Scope

Create all four layers in this exact order:

### 1. Zod schema — `packages/shared-types/src/schemas/<entity>.schema.ts`

```typescript
import { z } from 'zod';

export const Create<Entity>Schema = z.object({
  // entity-specific fields with validation
});

export const Update<Entity>Schema = Create<Entity>Schema.partial();

export const <Entity>ResponseSchema = Create<Entity>Schema.extend({
  _id: z.string(),
  userId: z.string(),
  createdAt: z.string().datetime(),
  updatedAt: z.string().datetime(),
});

export const <Entity>ListResponseSchema = z.array(<Entity>ResponseSchema);
```

Add inferred types to `packages/shared-types/src/types/index.ts`:
```typescript
export type Create<Entity>Input = z.infer<typeof Create<Entity>Schema>;
export type Update<Entity>Input = z.infer<typeof Update<Entity>Schema>;
export type <Entity>Response = z.infer<typeof <Entity>ResponseSchema>;
```

Export from `packages/shared-types/src/index.ts`.

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

- `userId: { type: Schema.Types.ObjectId, ref: 'User', required: true, index: true }`
- `timestamps: true` in schema options (handles `createdAt`/`updatedAt` automatically)
- Define compound indexes in the schema file
- Export: `export const <Entity>Model = model<I<Entity>>('<Entity>', <entity>Schema)`

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

Implement with full type signatures:
- `get<Entity>s(userId: string): Promise<I<Entity>[]>`
- `get<Entity>ById(id: string, userId: string): Promise<I<Entity>>` — throws `ApiError.notFound` if missing
- `create<Entity>(userId: string, data: Create<Entity>Input): Promise<I<Entity>>`
- `update<Entity>(id: string, userId: string, data: Update<Entity>Input): Promise<I<Entity>>` — throws `ApiError.notFound` if missing
- `delete<Entity>(id: string, userId: string): Promise<void>` — throws `ApiError.notFound` if missing

Rules:
- Every query includes `{ userId }` as a filter — no cross-user access
- Use `ApiError` static factories — never `throw new Error(...)`
- Never return `null` from get/update/delete — throw instead

### 4. Controller — `apps/api/src/controllers/<entity>.controller.ts`

- Import service and call exactly one method per handler
- Wrap every handler with `asyncHandler`
- Success: `res.status(200).json({ success: true, data: result })`
- Created: `res.status(201).json({ success: true, data: result })`
- Errors: use `ApiError` factories — never raw `throw new Error(...)`

### 5. Route — `apps/api/src/routes/<entity>.route.ts`

- Apply `authenticate` middleware before all routes
- Apply `validate` middleware with Zod schemas from `@wealthwise/shared-types`
- Add JSDoc Swagger annotations for every endpoint
- Register the router in `apps/api/src/routes/index.ts`

## Standards

- No `any` types
- Named exports only
- `const` over `let`, never `var`
- `async/await` over raw Promises

## Verification

After scaffolding, run:
```bash
npx turbo lint --filter=@wealthwise/api
npx turbo test --filter=@wealthwise/api
```

Report all created file paths and the registered route paths (e.g., `GET /api/v1/<entities>`).

