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 any types (use unknown if needed)
  • Zod schemas for API inputs
  • Prisma types for DB entities
  • Tests cover new functionality
  • npm run typecheck passes
  • npm run lint passes
Weekly Installs
4
First Seen
3 days ago
Installed on
codex2
claude-code2
antigravity2
gemini-cli2
windsurf1
trae1