Code Generation
@xond/api generates type-safe REST services, controllers, and frontend models from your Prisma schema. This section explains how the generation process works and what gets created.
Overview
The code generator reads your prisma/schema.prisma file, parses all models and their relationships, and generates:
- Backend REST services – Full CRUD operations with filtering, pagination, and sorting
- Frontend TypeScript models – Type definitions and column metadata for UI components
- Service registry – Centralized registry for accessing all services
- Type mappings – Field type maps for form generation
Generated Files Structure
After running xond-api generate, your project structure will look like this:
your-api-app/
├── src/
│ └── modules/
│ └── json/
│ └── services/
│ └── generated/
│ ├── userService.ts
│ ├── productService.ts
│ ├── serviceRegistry.ts
│ └── fieldTypeMap.ts
└── your-ui-app/
└── src/
└── generated/
└── models/
├── userModel.tsx
├── productModel.tsx
└── index.ts
Backend Services
Each model in your Prisma schema generates a corresponding service file.
Service File Structure
// src/modules/json/services/generated/userService.ts
import { PrismaClient } from "@prisma/client";
import { normalizeArrayParam } from "@xond/api/lib/util";
export const getUser = async (prisma: PrismaClient, id: string) => {
// Implementation
};
export const getUsers = async (
prisma: PrismaClient,
params: {
page?: number;
pageSize?: number;
sortBy?: string;
sortOrder?: "asc" | "desc";
filters?: Record<string, any>;
}
) => {
// Implementation with pagination, sorting, filtering
};
export const createUser = async (prisma: PrismaClient, data: any) => {
// Implementation
};
export const updateUser = async (prisma: PrismaClient, id: string, data: any) => {
// Implementation
};
export const deleteUser = async (prisma: PrismaClient, id: string) => {
// Implementation
};
Service Features
Each generated service includes:
- CRUD operations – Create, Read, Update, Delete
- Pagination –
pageandpageSizeparameters - Sorting –
sortByandsortOrderparameters - Filtering – Dynamic filter building from query parameters
- Relations – Automatic handling of Prisma relations
- Type safety – Full TypeScript type inference
Service Registry
All services are registered in serviceRegistry.ts:
import * as userService from "./userService";
import * as productService from "./productService";
export const serviceRegistry = {
"user": userService,
"product": productService,
};
This allows dynamic service access:
import { serviceRegistry } from "./generated/serviceRegistry";
const userService = serviceRegistry["user"];
const users = await userService.getUsers(prisma, { page: 1, pageSize: 10 });
Frontend Models
Frontend models provide TypeScript types and metadata for UI components.
Model File Structure
// src/generated/models/userModel.tsx
export interface User {
id: string;
name: string;
email: string;
createdAt: Date;
}
export interface UserColumnModel {
id: { label: string; type: string; required: boolean };
name: { label: string; type: string; required: boolean };
email: { label: string; type: string; required: boolean };
createdAt: { label: string; type: string; required: boolean };
}
export const UserModel: UserColumnModel = {
id: { label: "ID", type: "uuid", required: true },
name: { label: "Name", type: "string", required: true },
email: { label: "Email", type: "string", required: true },
createdAt: { label: "Created At", type: "datetime", required: false },
};
export const UserColumnModel: UserColumnModel = UserModel;
Model Registry
Models are exported and registered in index.ts:
export * from "./userModel";
export * from "./productModel";
export const modelRegistry = {
user: UserModel,
product: ProductModel,
};
export const columnModelRegistry = {
user: UserColumnModel,
product: ProductColumnModel,
};
Field Type Mapping
The generator creates fieldTypeMap.ts which maps Prisma field types to form input types:
export const fieldTypeMap = {
user: {
id: "uuid",
name: "text",
email: "email",
createdAt: "datetime",
},
product: {
id: "uuid",
name: "text",
price: "money",
quantity: "number",
},
};
This is used by @xond/ui components to automatically render the correct input types.
Customization
Skipping Models
You can skip certain models from generation by configuring skipTables in src/config/customConfig.ts:
export const customConfig = {
skipTables: ["_Migration", "AuditLog"],
};
Model Overrides
You can customize how models are parsed by providing an override function:
// src/config/customConfig.ts
export const overrideModel = (models: Model[]) => {
// Customize models before generation
return models.map(model => {
if (model.name === "User") {
// Add custom fields or modify structure
}
return model;
});
};
Regeneration
You can safely regenerate code multiple times. The generator:
- Overwrites generated service files
- Preserves custom code in separate files
- Updates registries and indexes automatically
Best practice: Keep custom business logic in separate files, not in generated files.
Integration with Express
Generated services are designed to work with Express routes:
import { serviceRegistry } from "./modules/json/services/generated/serviceRegistry";
import { PrismaClient } from "@prisma/client";
const prisma = new PrismaClient();
app.get("/api/users", async (req, res) => {
const users = await serviceRegistry["user"].getUsers(prisma, {
page: parseInt(req.query.page as string) || 1,
pageSize: parseInt(req.query.pageSize as string) || 10,
sortBy: req.query.sortBy as string,
sortOrder: req.query.sortOrder as "asc" | "desc",
});
res.json(users);
});
Next Steps
- Learn about configuration options
- Explore reverse engineering
- See architecture overview