Vibe Coding Codex · CH 22

Chapter 22: TypeScript for Vibe Coders — Type Safety Without the Pain

⏱️ 3 min read·Updated Jul 2026

TypeScript: Friend or Foe?

Many vibe coders avoid TypeScript. It adds friction. The AI occasionally generates code with type errors. Fixing type errors feels like fighting the tools instead of building the product.

But this is a false economy. TypeScript catches a specific category of bugs — type errors — before they reach production. And in an AI-assisted workflow where the AI is generating significant portions of your code, type errors are more common than in hand-written code.

The key is using TypeScript at the right level of strictness for your project phase.

---

The Two-Phase TypeScript Strategy

Phase 1 (Building fast, first version): Use TypeScript but configure it loosely. Focus on getting the product working.

Phase 2 (Stabilising for production): Tighten type safety on the paths that matter most — auth, payments, API routes.

json
// tsconfig.json — Phase 1 (Lenient)
{
  "compilerOptions": {
    "strict": false,
    "noImplicitAny": false,
    "strictNullChecks": false
  }
}

// tsconfig.json — Phase 2 (Stricter)
{
  "compilerOptions": {
    "strict": true,
    "noImplicitAny": true,
    "strictNullChecks": true,
    "noUnusedLocals": true
  }
}

---

The Types That Pay the Biggest Dividend

1. API Response Types

Define the exact shape of what your API routes return. Every component that consumes your API benefits:

```typescript // types/api.ts export type ApiResponse = { data: T; error: null; } | { data: null; error: { message: string; code: string }; };

// Usage in API route: export async function GET(): Promise { try { const data = await fetchPosts();

return Response.json({ data, error: null } satisfies ApiResponse); } catch (err) { return Response.json({ data: null, error: { message: err.message, code: 'FETCH_ERROR' } }, { status: 500 }); } }

// Usage in client component — TypeScript knows exactly what you get: const { data: posts, error } = await fetchFromApi('/api/posts'); if (error) showError(error.message); // posts is now Post[] | null — TypeScript enforces the null check

code
### 2. Database Record Types

Generate types directly from your Supabase schema:
bash

Generate TypeScript types from your live Supabase database

npx supabase gen types typescript --project-id your-project-id > types/database.types.ts
code
This gives you a `Database` type that covers every table, view, and function in your database. Feed this to the Supabase client for fully typed queries:
typescript import { createClient } from '@supabase/supabase-js'; import type { Database } from '@/types/database.types';

const supabase = createClient(url, key);

// Now fully typed — TypeScript knows exactly what columns exist const { data: posts } = await supabase .from('posts') .select('id, title, status, created_at') .eq('status', 'published'); // TypeScript validates 'status' is a valid column // posts is { id: string; title: string; status: string; created_at: string }[] | null

code
### 3. Server Action and API Route Input Validation

Use Zod for runtime validation + TypeScript type inference in one step:
typescript import { z } from 'zod';

const CreatePostSchema = z.object({ title: z.string().min(3).max(200), content: z.string().min(10), status: z.enum(['draft', 'published']), });

// TypeScript infers the type automatically from the schema type CreatePostInput = z.infer;

// In your API route: export async function POST(request: Request) { const body = await request.json(); const parsed = CreatePostSchema.safeParse(body); if (!parsed.success) { return Response.json({ error: parsed.error.flatten() }, { status: 400 }); } // parsed.data is now fully typed as CreatePostInput const post = await createPost(parsed.data); return Response.json({ data: post }); }

code
---

## Prompting AI for TypeScript Code

The key to getting good TypeScript from AI is to specify your type requirements explicitly:
Write a TypeScript function that [does X].

Types required:

  • Use this existing type for input: [paste type]
  • Return type should be: Promise> using our ApiResponse generic
  • Do not use 'any' — use 'unknown' with type narrowing if needed
  • Include proper error handling with typed error returns
Existing types context:
code
[paste your types/api.ts and types/database.types.ts]
```

When you give the AI your existing types as context, it generates code that integrates cleanly with your existing type system rather than inventing its own.

Finished reading this chapter?

Mark it completed to update your AI Builder skill profile score on your dashboard.

📋

Download Printable Chapter Checklist

Get a print-friendly, formatted checklist of workspace actions to apply this chapter offline.