learning_ai_common_plat/AI.dev/PROMPTS/backend-module-crud.md
saravanakumardb 32c7b1ba7e docs(prompts): add 14 reusable AI prompts for ecosystem-wide workflows
- roadmap-execution: phased roadmap execution with checkpoints
- new-product-scaffold: scaffold new ByteLyst product repos
- prd-to-implementation: convert PRDs to concrete plans
- cross-repo-debug: systematic multi-repo debugging
- backend-module-crud: Fastify CRUD modules (types/repo/routes/tests)
- platform-integration: wire products into common platform
- refactor-with-tests: test-first safe refactoring
- test-gap-analysis: coverage gap identification and remediation
- type-safety-sweep: TypeScript error triage and fix
- dependency-health-check: cross-repo dependency audit
- pre-release-validation: comprehensive release checklist
- docker-production-prep: production Docker images
- agents-md-sync: keep AI instruction files accurate
- ecosystem-audit: full ecosystem health dashboard
2026-05-17 16:48:58 -07:00

6.8 KiB

name description argument-hint agent
backend-module-crud Add a complete Fastify CRUD module with types, repository, routes, and tests following ByteLyst conventions. Module name and parent repo, e.g. "habits in efforise", "workouts in peakpulse", "conversations in jarvisjr" agent

Backend Module CRUD Prompt

Create a complete Fastify backend module with Zod-validated types, Cosmos repository, REST routes, and comprehensive tests.

Context — ByteLyst Backend Pattern

Every product backend follows this module structure:

backend/src/modules/<module>/
├── types.ts          # Zod schemas + TypeScript interfaces
├── repository.ts     # Cosmos DB CRUD operations
├── routes.ts         # Fastify REST endpoints
└── <module>.test.ts  # Vitest tests

Key packages used:

  • @bytelyst/datastore — Cosmos DB abstraction (getCollection())
  • @bytelyst/errors — Typed HTTP errors (BadRequestError, NotFoundError, etc.)
  • @bytelyst/fastify-auth — JWT auth middleware
  • zod — Schema validation (service's own copy, NOT from @bytelyst/config)

Implementation Steps

1. Create types.ts

import { z } from 'zod';

// ── Create schema (input from API) ──
export const create<Entity>Schema = z.object({
  // Required fields
  name: z.string().min(1).max(200),
  // Optional fields
  description: z.string().max(1000).optional(),
  // Enum fields
  status: z.enum(['active', 'paused', 'completed']).default('active'),
});

// ── Update schema (partial, for PATCH) ──
export const update<Entity>Schema = create<Entity>Schema.partial();

// ── Full document (stored in Cosmos) ──
export const <entity>Schema = create<Entity>Schema.extend({
  id: z.string().uuid(),
  userId: z.string(),
  productId: z.string(),
  createdAt: z.string().datetime(),
  updatedAt: z.string().datetime(),
});

// ── TypeScript types ──
export type Create<Entity> = z.infer<typeof create<Entity>Schema>;
export type Update<Entity> = z.infer<typeof update<Entity>Schema>;
export type <Entity> = z.infer<typeof <entity>Schema>;

2. Create repository.ts

import { getCollection } from '../../lib/datastore.js';
import type { Create<Entity>, Update<Entity>, <Entity> } from './types.js';
import { NotFoundError } from '../../lib/errors.js';
import { randomUUID } from 'node:crypto';

const CONTAINER = '<product>_<entities>';

function getContainer() {
  return getCollection(CONTAINER);
}

export async function create<Entity>(
  userId: string,
  productId: string,
  data: Create<Entity>
): Promise<<Entity>> {
  const now = new Date().toISOString();
  const doc: <Entity> = {
    ...data,
    id: randomUUID(),
    userId,
    productId,
    createdAt: now,
    updatedAt: now,
  };
  const container = getContainer();
  await container.create(doc);
  return doc;
}

export async function list<Entities>(
  userId: string,
  productId: string
): Promise<<Entity>[]> {
  const container = getContainer();
  return container.query<Entity>({
    query: 'SELECT * FROM c WHERE c.userId = @userId AND c.productId = @productId ORDER BY c.createdAt DESC',
    parameters: [
      { name: '@userId', value: userId },
      { name: '@productId', value: productId },
    ],
  });
}

export async function get<Entity>ById(
  id: string,
  userId: string,
  productId: string
): Promise<<Entity>> {
  const container = getContainer();
  const doc = await container.read<Entity>(id, userId);
  if (!doc || doc.productId !== productId) {
    throw new NotFoundError('<Entity> not found');
  }
  return doc;
}

export async function update<Entity>(
  id: string,
  userId: string,
  productId: string,
  data: Update<Entity>
): Promise<<Entity>> {
  const existing = await get<Entity>ById(id, userId, productId);
  const updated: <Entity> = {
    ...existing,
    ...data,
    updatedAt: new Date().toISOString(),
  };
  const container = getContainer();
  await container.replace(id, updated, userId);
  return updated;
}

export async function delete<Entity>(
  id: string,
  userId: string,
  productId: string
): Promise<void> {
  await get<Entity>ById(id, userId, productId); // Verify ownership
  const container = getContainer();
  await container.delete(id, userId);
}

3. Create routes.ts

import type { FastifyInstance } from 'fastify';
import { create<Entity>Schema, update<Entity>Schema } from './types.js';
import * as repo from './repository.js';
import { getUserId, getRequestProductId } from '../../lib/request-context.js';

export default async function <entity>Routes(app: FastifyInstance) {
  // GET /api/<entities>
  app.get('/<entities>', async (req) => {
    const userId = getUserId(req);
    const productId = getRequestProductId(req);
    return repo.list<Entities>(userId, productId);
  });

  // GET /api/<entities>/:id
  app.get('/<entities>/:id', async (req) => {
    const { id } = req.params as { id: string };
    const userId = getUserId(req);
    const productId = getRequestProductId(req);
    return repo.get<Entity>ById(id, userId, productId);
  });

  // POST /api/<entities>
  app.post('/<entities>', async (req, reply) => {
    const userId = getUserId(req);
    const productId = getRequestProductId(req);
    const data = create<Entity>Schema.parse(req.body);
    const entity = await repo.create<Entity>(userId, productId, data);
    reply.code(201);
    return entity;
  });

  // PATCH /api/<entities>/:id
  app.patch('/<entities>/:id', async (req) => {
    const { id } = req.params as { id: string };
    const userId = getUserId(req);
    const productId = getRequestProductId(req);
    const data = update<Entity>Schema.parse(req.body);
    return repo.update<Entity>(id, userId, productId, data);
  });

  // DELETE /api/<entities>/:id
  app.delete('/<entities>/:id', async (req, reply) => {
    const { id } = req.params as { id: string };
    const userId = getUserId(req);
    const productId = getRequestProductId(req);
    await repo.delete<Entity>(id, userId, productId);
    reply.code(204);
  });
}

4. Register in server.ts

import <entity>Routes from './modules/<entities>/routes.js';
// ...
await app.register(<entity>Routes, { prefix: '/api' });

5. Create tests

Write tests covering:

  • Create with valid data → 201
  • Create with invalid data → 400
  • List returns user's items only
  • Get by ID returns correct item
  • Get by ID with wrong user → 404
  • Update modifies fields + updatedAt
  • Delete removes item → 204
  • Delete with wrong user → 404

6. Validate

cd backend
pnpm test
pnpm typecheck
pnpm build

7. Commit

git add .
git commit -m "feat(<entities>): add <entity> CRUD module

- types.ts: Zod schemas for create/update/<entity>
- repository.ts: Cosmos CRUD with user ownership
- routes.ts: GET/POST/PATCH/DELETE endpoints
- tests: N tests covering CRUD + auth + validation"
git push