Advanced Usage
This guide covers advanced usage of CrudKit, including customization, optimization, and handling complex scenarios.
Custom Validation with Zod
You can enhance your schema with custom validation rules:
// schema.ts
import { pgTable, serial, text, timestamp, integer } from 'drizzle-orm/pg-core';
import { generateSchemas } from 'crudkit';
import { z } from 'zod';
export const products = pgTable('products', {
id: serial('id').primaryKey(),
name: text('name').notNull(),
price: integer('price').notNull(),
stock: integer('stock').notNull(),
createdAt: timestamp('created_at').defaultNow(),
updatedAt: timestamp('updated_at'),
});
// Custom schema overrides
export const productSchemas = generateSchemas({
table: products,
overrides: {
insert: {
name: z.string().min(3).max(100),
price: z.number().positive(),
stock: z.number().int().nonnegative(),
},
update: {
price: z.number().positive().optional(),
},
},
});
Working with Relationships
Handling relationships between tables:
// schema.ts
import { pgTable, serial, text, timestamp, integer, uuid } from 'drizzle-orm/pg-core';
import { relations } from 'drizzle-orm';
export const categories = pgTable('categories', {
id: serial('id').primaryKey(),
name: text('name').notNull(),
});
export const products = pgTable('products', {
id: serial('id').primaryKey(),
name: text('name').notNull(),
categoryId: integer('category_id').references(() => categories.id),
});
export const productsRelations = relations(products, ({ one }) => ({
category: one(categories, {
fields: [products.categoryId],
references: [categories.id],
}),
}));
Then in your router:
// api/routers/products.ts
import { createTRPCRouter, publicProcedure } from '../trpc';
import { generateRouter } from 'crudkit';
import { db } from '../db';
import { products } from '../schema';
export const productsRouter = generateRouter(
{
table: products,
customProcedures: {
getWithCategory: publicProcedure
.input(z.object({ id: z.string() }))
.query(async ({ input, ctx }) => {
return await ctx.db.query.products.findFirst({
where: eq(products.id, input.id),
with: {
category: true,
},
});
}),
},
},
{
createTRPCRouter,
publicProcedure,
protectedProcedure,
}
);
Advanced React Query Patterns
Invalidating Queries
import { useProducts } from '../hooks/use-products';
export function DeleteProduct({ id }) {
const utils = trpc.useContext();
const { delete: deleteProduct, isLoading } = useProducts.useDelete();
const handleDelete = async () => {
await deleteProduct({ id });
// Invalidate the getAll query to refresh the list
utils.products.getAll.invalidate();
};
return (
<button onClick={handleDelete} disabled={isLoading}>
{isLoading ? 'Deleting...' : 'Delete'}
</button>
);
}
Optimistic Updates
import { useProducts } from '../hooks/use-products';
export function UpdateStock({ product }) {
const utils = trpc.useContext();
const { update, isLoading } = useProducts.useUpdate();
const increaseStock = () => {
// Get current data
const previousData = utils.products.getById.getData({ id: product.id });
// Optimistically update the UI
utils.products.getById.setData(
{ id: product.id },
{ ...previousData, stock: previousData.stock + 1 }
);
// Send actual update to server
update({
id: product.id,
stock: previousData.stock + 1
}, {
// If it fails, revert to previous data
onError: () => {
utils.products.getById.setData({ id: product.id }, previousData);
}
});
};
return (
<button onClick={increaseStock} disabled={isLoading}>
Increase Stock
</button>
);
}
Custom Error Handling
import { generateRouter } from 'crudkit';
import { TRPCError } from '@trpc/server';
export const productsRouter = generateRouter(
{
table: products,
customProcedures: {
delete: protectedProcedure
.input(z.object({ id: z.string() }))
.mutation(async ({ input, ctx }) => {
// Check if the product exists
const product = await ctx.db.query.products.findFirst({
where: eq(products.id, input.id),
});
if (!product) {
throw new TRPCError({
code: 'NOT_FOUND',
message: 'Product not found',
});
}
// Check if the user has permission
if (product.userId !== ctx.user.id) {
throw new TRPCError({
code: 'FORBIDDEN',
message: 'You do not have permission to delete this product',
});
}
// Proceed with delete
return await ctx.db.delete(products)
.where(eq(products.id, input.id))
.returning();
}),
},
},
helpers
);
Next Steps
Now that you’re familiar with advanced usage patterns, check out our Examples to see full implementations of CrudKit in action.
Last updated on