Skip to main content

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

src/modules/<module>/api/<path>/route.ts

Example: api/organizations/invite/route.ts handles /api/organizations/invite.

Each file exports handlers for HTTP methods (GET, POST, PUT, DELETE, etc.) following Next.js App Router conventions.

api/organizations/invite/route.ts
import { NextResponse } from "next/server";
import { z } from "zod";
import { createRequestContainer } from "@/lib/di/container";
import type { AuthContext } from "@saasframe/shared/lib/auth/server";

const inputSchema = z.object({
email: z.string().email(),
roleId: z.string().uuid(),
});

export async function POST(req: Request, { auth }: { auth: AuthContext }) {
const body = inputSchema.parse(await req.json());

const container = await createRequestContainer();
const invitations = container.resolve("organizationInvitationService");

await invitations.inviteUser({
...body,
organizationId: auth.organizationId,
});

return NextResponse.json({ ok: true });
}

export const metadata = {
POST: {
requireAuth: true,
requireFeatures: ["directory.invite"],
},
};

Best practices

  • Validate all inputs using Zod schemas colocated with the entity or service they touch.
  • Respect tenant boundaries by scoping queries to organizationId or tenantId from 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.