TypeScript conventions for ecommerce monorepo. Covers Next.js, Fastify, Prisma, Zod validation, React Query, and testing patterns. Triggers on "typescript", "type error", "ts", "tsx", "type hints", "zod schema", "prisma", "react query", "next.js", "fastify routes", "api types". PROACTIVE: MUST invoke when writing or editing .ts or .tsx files.
Install
npx skillscat add lorenzogirardi/ai-ecom-demo/typescript Install via the SkillsCat registry.
SKILL.md
ABOUTME: TypeScript skill for ecommerce monorepo with Next.js and Fastify
ABOUTME: Covers type patterns, Prisma, Zod, React Query, and testing conventions
TypeScript Skill (Ecommerce)
Quick Reference
| Rule | Convention |
|---|---|
| Strict mode | Always enabled |
| Null checks | strictNullChecks: true |
| Return types | Explicit for public APIs |
| Zod schemas | Validation at boundaries |
| Prisma types | Auto-generated, never manual |
Project Structure
apps/
├── frontend/ # Next.js 16
│ ├── src/
│ │ ├── app/ # App Router pages
│ │ ├── components/ # React components
│ │ ├── hooks/ # Custom hooks (useProducts, useCart, etc.)
│ │ ├── lib/ # Utilities (api.ts, auth-context.tsx)
│ │ └── types/ # Type definitions
│ └── tests/ # Frontend tests
│
└── backend/ # Fastify API
├── src/
│ ├── config/ # Configuration
│ ├── middleware/ # Auth guard, error handler
│ ├── modules/ # Feature modules (auth, catalog, orders)
│ └── utils/ # Prisma, Redis, logger
├── prisma/ # Schema and migrations
└── tests/ # Backend testsType Patterns
API Response Types
// types/api.ts
export interface ApiResponse<T> {
data: T;
meta?: {
total: number;
page: number;
pageSize: number;
};
}
export interface ApiError {
statusCode: number;
error: string;
message: string;
}Zod Schemas (Validation)
// schemas/product.ts
import { z } from 'zod';
export const ProductSchema = z.object({
id: z.string().uuid(),
name: z.string().min(1).max(255),
price: z.number().positive(),
categoryId: z.string().uuid(),
});
export type Product = z.infer<typeof ProductSchema>;
// Use at API boundaries
const validated = ProductSchema.parse(requestBody);Prisma Integration
// DON'T manually define DB types
// DO use Prisma generated types
import { User, Product, Order } from '@prisma/client';
// Include relations explicitly
import { Prisma } from '@prisma/client';
type OrderWithItems = Prisma.OrderGetPayload<{
include: { items: { include: { product: true } } };
}>;React Patterns
Custom Hooks
// hooks/useProducts.ts
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
export function useProducts(categoryId?: string) {
return useQuery({
queryKey: ['products', categoryId],
queryFn: () => api.getProducts(categoryId),
staleTime: 5 * 60 * 1000, // 5 minutes
});
}
export function useCreateProduct() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: api.createProduct,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['products'] });
},
});
}Component Props
// components/ProductCard.tsx
interface ProductCardProps {
product: Product;
onAddToCart?: (productId: string) => void;
className?: string;
}
export function ProductCard({ product, onAddToCart, className }: ProductCardProps) {
// ...
}Fastify Patterns
Route Types
// modules/catalog/catalog.routes.ts
import { FastifyPluginAsync } from 'fastify';
import { z } from 'zod';
const GetProductParams = z.object({
id: z.string().uuid(),
});
const catalogRoutes: FastifyPluginAsync = async (fastify) => {
fastify.get<{
Params: z.infer<typeof GetProductParams>;
}>('/products/:id', {
schema: {
params: GetProductParams,
},
}, async (request, reply) => {
const { id } = request.params;
// ...
});
};
export default catalogRoutes;Error Handling
// middleware/error-handler.ts
import { FastifyError, FastifyReply, FastifyRequest } from 'fastify';
export function errorHandler(
error: FastifyError,
request: FastifyRequest,
reply: FastifyReply
) {
const statusCode = error.statusCode ?? 500;
reply.status(statusCode).send({
statusCode,
error: error.name,
message: error.message,
});
}Testing Patterns
Unit Tests (Vitest)
// tests/hooks/useProducts.test.tsx
import { renderHook, waitFor } from '@testing-library/react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { useProducts } from '@/hooks/useProducts';
const wrapper = ({ children }: { children: React.ReactNode }) => (
<QueryClientProvider client={new QueryClient()}>
{children}
</QueryClientProvider>
);
describe('useProducts', () => {
it('fetches products', async () => {
const { result } = renderHook(() => useProducts(), { wrapper });
await waitFor(() => {
expect(result.current.isSuccess).toBe(true);
});
expect(result.current.data).toHaveLength(18);
});
});Integration Tests (Testcontainers)
// tests/integration/catalog.test.ts
import { PostgreSqlContainer } from '@testcontainers/postgresql';
import { buildApp } from '../../src/app';
describe('Catalog API', () => {
let container: StartedPostgreSqlContainer;
let app: FastifyInstance;
beforeAll(async () => {
container = await new PostgreSqlContainer().start();
process.env.DATABASE_URL = container.getConnectionUri();
app = await buildApp();
});
afterAll(async () => {
await app.close();
await container.stop();
});
it('GET /products returns products', async () => {
const response = await app.inject({
method: 'GET',
url: '/api/products',
});
expect(response.statusCode).toBe(200);
});
});Commands
# Type checking
npm run typecheck # All workspaces
npm run typecheck -w apps/backend
# Linting
npm run lint
npm run lint:fix
# Testing
npm run test # All tests
npm run test -w apps/backend
# Prisma
npm run db:generate -w apps/backend # Generate types
npm run db:push -w apps/backend # Push schema
npm run db:seed -w apps/backend # Seed dataAnti-Patterns
| Anti-Pattern | Problem | Solution |
|---|---|---|
any type |
No type safety | Use unknown + type guards |
| Manual DB types | Drift from schema | Use Prisma generated types |
| Implicit returns | Unclear API | Explicit return types |
| No validation | Runtime errors | Zod at boundaries |
// @ts-ignore |
Hidden bugs | Fix the type issue |
Checklist
Before committing TypeScript changes:
- No
anytypes (useunknownif needed) - Zod schemas for API inputs
- Prisma types for DB entities
- Tests cover new functionality
-
npm run typecheckpasses -
npm run lintpasses