Extend module APIs
While the CRUD factory covers most cases, sometimes you need bespoke endpoints—aggregations, external integrations, or webhooks. Follow these guidelines to extend module APIs safely.
File structure
packages/<module>/src/modules/<module>/api/<method>/<path>.ts
Example: api/post/organizations/invite.ts handles POST /api/organizations/invite.
Each handler exports an async function that receives the request, response helpers, and the request-scoped DI container:
api/post/organizations/invite.ts
import { z } from 'zod';
import type { ApiHandler } from '@open-mercato/shared/modules/api/types';
const inputSchema = z.object({
email: z.string().email(),
roleId: z.string().uuid(),
});
const handler: ApiHandler = async ({ request, container, organization }) => {
const body = inputSchema.parse(await request.json());
const invitations = container.resolve('organizationInvitationService');
await invitations.inviteUser({ ...body, organizationId: organization.id });
return Response.json({ ok: true });
};
export const metadata = {
requireAuth: true,
requireFeatures: ['directory.invite'],
};
export default handler;
Best practices
- Validate all inputs using Zod schemas colocated with the entity or service they touch.
- Respect tenant boundaries by scoping queries to
organizationIdortenantIdfrom the request context. - Use services, not repositories directly – resolve them from DI so you can unit test independently and override implementations per tenant.
- Emit events deliberately when downstream modules should react; otherwise keep handlers idempotent.
- Document metadata (
requireFeatures,requireRoles) to integrate with admin navigation and ACLs.
Mixing CRUD factories and bespoke endpoints gives you the flexibility to move fast while keeping the platform predictable.