| name | api-endpoint |
| description | 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.
|
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
import { z } from 'zod';
export const Create<Entity>Schema = z.object({
});
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:
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:
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>).