typescript
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 tests
Type 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 data
Anti-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
Weekly Installs
4
Repository
lorenzogirardi/ai-ecom-demoFirst Seen
3 days ago
Installed on
codex2
claude-code2
antigravity2
gemini-cli2
windsurf1
trae1