---
title: "Type-Safe Data Layer"
description: "Build a robust data layer using Zod for runtime validation and type inference. Define Product and Review schemas, create sample data, and implement type-safe data access functions."
canonical_url: "https://vercel.com/academy/ai-summary-app-with-nextjs/type-safe-data-layer"
md_url: "https://vercel.com/academy/ai-summary-app-with-nextjs/type-safe-data-layer.md"
docset_id: "vercel-academy"
doc_version: "1.0"
last_updated: "2026-04-11T11:43:36.931Z"
content_type: "lesson"
course: "ai-summary-app-with-nextjs"
course_title: "Creating an AI Summary App with Next.js"
prerequisites:  []
---

<agent-instructions>
Vercel Academy — structured learning, not reference docs.
Lessons are sequenced.
Adapt commands to the human's actual environment (OS, package manager, shell, editor) — detect from project context or ask, don't assume.
The lesson shows one path; if the human's project diverges, adapt concepts to their setup.
Preserve the learning goal over literal steps.
Quizzes are pedagogical — engage, don't spoil.
Quiz answers are included for your reference.
</agent-instructions>

# Type-Safe Data Layer

# Type-Safe data layer

TypeScript gives you compile-time type safety, but it can't validate data at runtime. Zod provides both: runtime validation AND TypeScript types inferred from your schemas. This catches bugs early and makes your code reliable.

## Outcome

Create a type-safe data layer with Zod schemas for products and reviews, populate it with sample data, and implement helper functions for data access.

## Fast Track

1. Run `pnpm add zod` and create `lib/types.ts` with `ReviewSchema` and `ProductSchema`
2. Create `lib/sample-data.ts` with 3 products, each having 3-4 reviews with varied ratings
3. Add `getProducts()` and `getProduct(slug)` functions, then update `app/page.tsx` to display product cards

## Hands-on Exercise 1.2

Build a type-safe data layer for product reviews:

**Requirements:**

1. Install Zod for schema validation
2. Create schemas for Review and Product types
3. Add sample data for 3-5 products with multiple reviews each
4. Create helper functions: `getProducts()` and `getProduct(id)`
5. Export TypeScript types inferred from Zod schemas

**Implementation hints:**

- Use `z.object()` to define schemas
- Use `z.infer<typeof schema>` to generate TypeScript types
- Include fields: product name, slug, description, reviews array
- Each review needs: reviewer name, stars (1-5), text, date
- Sample data should feel realistic (varied ratings, detailed reviews)

## Step 1: Install Zod

```bash
pnpm add zod
```

Zod is a TypeScript-first schema validation library with zero dependencies.

## Step 2: Create Type Schemas

Create `lib/types.ts`:

```typescript title="lib/types.ts"
import { z } from "zod";

// Review schema
export const ReviewSchema = z.object({
  reviewer: z.string(),
  stars: z.number().min(1).max(5),
  review: z.string(),
  date: z.string(), // ISO date string
});

// Product schema
export const ProductSchema = z.object({
  slug: z.string(),
  name: z.string(),
  description: z.string(),
  reviews: z.array(ReviewSchema),
});

// Infer TypeScript types from schemas
export type Review = z.infer<typeof ReviewSchema>;
export type Product = z.infer<typeof ProductSchema>;
```

**What this gives you:**

- Runtime validation with `.parse()` or `.safeParse()`
- Automatic TypeScript types
- Compile-time AND runtime safety

## Step 3: Create Sample Data

Create `lib/sample-data.ts`:

```typescript title="lib/sample-data.ts"
import { Product, ProductSchema } from "./types";

export const sampleProductsReviews: Record<string, Product> = {
  mower: {
    slug: "mower",
    name: "Mower3000",
    description: "Autonomous robotic lawn mower with smart navigation",
    reviews: [
      {
        reviewer: "John D.",
        stars: 4,
        review:
          "Great mower! Handles slopes well and is very quiet. Setup took about an hour, but once configured it works autonomously. Battery lasts about 90 minutes.",
        date: "2025-11-15T10:30:00Z",
      },
      {
        reviewer: "Sarah M.",
        stars: 5,
        review:
          "Love this thing! My lawn has never looked better. It runs every day at 6am and I don't have to think about it. The app is easy to use and scheduling is straightforward.",
        date: "2025-11-20T14:22:00Z",
      },
      {
        reviewer: "Mike R.",
        stars: 2,
        review:
          "Disappointed. I hate mowing the lawn, and this did not change that.",
        date: "2025-11-28T08:15:00Z",
      },
      {
        reviewer: "Emily K.",
        stars: 4,
        review:
          "Really impressed with the cutting quality. It mulches the grass perfectly. Only downside is it can't handle thick weeds, but that's expected. Worth the price.",
        date: "2025-12-01T16:45:00Z",
      },
    ],
  },
  ecoBright: {
    slug: "ecoBright",
    name: "EcoBright LED Bulbs",
    description: "Energy-efficient smart LED bulbs with color temperature control",
    reviews: [
      {
        reviewer: "Amanda L.",
        stars: 5,
        review:
          "These bulbs are fantastic! Added a lot of ambiance to the room.",
        date: "2025-11-10T09:20:00Z",
      },
      {
        reviewer: "Carlos P.",
        stars: 3,
        review:
          "Decent bulbs for the price. Color temperature control works well, but I wish they were brighter at max setting. They do save energy compared to my old bulbs.",
        date: "2025-11-18T12:33:00Z",
      },
      {
        reviewer: "Lisa T.",
        stars: 4,
        review:
          "Very happy with these. The scheduling feature is great—bulbs dim automatically at 9pm. App is intuitive. Lost one star because one bulb failed after 3 months.",
        date: "2025-11-25T18:10:00Z",
      },
    ],
  },
  aquaHeat: {
    slug: "aquaHeat",
    name: "AquaHeat Tankless Water Heater",
    description: "High-efficiency tankless water heater with digital temperature control",
    reviews: [
      {
        reviewer: "Robert F.",
        stars: 5,
        review:
          "Incredible upgrade from our old tank heater. Endless hot water and our energy bill dropped by 30%. Installation was professional and took about 4 hours.",
        date: "2025-10-05T11:15:00Z",
      },
      {
        reviewer: "Jenny W.",
        stars: 4,
        review:
          "Works great but required upgrading our gas line which added $800 to the cost. Once installed, it's been flawless. Water heats instantly and temperature is consistent.",
        date: "2025-10-20T15:40:00Z",
      },
      {
        reviewer: "Tom H.",
        stars: 3,
        review:
          "Good product but overpriced. It works as advertised but the 'energy savings' haven't been as dramatic as claimed. Still, no more running out of hot water is nice.",
        date: "2025-11-12T07:55:00Z",
      },
      {
        reviewer: "Maria S.",
        stars: 5,
        review:
          "Best home improvement we've made! Compact design freed up space in our utility room. The digital display is clear and adjusting temperature is easy. Highly recommend.",
        date: "2025-11-30T13:25:00Z",
      },
    ],
  },
};

// Validate data at runtime
Object.values(sampleProductsReviews).forEach((product) => {
  ProductSchema.parse(product);
});

export const Products = Object.values(sampleProductsReviews);
```

**What this provides:**

- 3 products with varied reviews
- Realistic review content and ratings
- Runtime validation (throws if data is malformed)

## Step 4: Create Helper Functions

Add data access functions to `lib/sample-data.ts`:

```typescript title="lib/sample-data.ts"
export const Products = Object.values(sampleProductsReviews);

/**
 * Add beneath the Products export
 */
export function getProducts(): Product[] {
  return Products;
}

/**
 * Get a single product by slug
 * @throws Error if product not found
 */
export function getProduct(slug: string): Product {
  const product = sampleProductsReviews[slug];

  if (!product) {
    throw new Error(`Product not found: ${slug}`);
  }

  return product;
}
```

**Type safety benefits:**

- `getProducts()` returns `Product[]` (fully typed)
- `getProduct()` returns `Product` (throws if not found)
- TypeScript autocomplete works everywhere

## Try It

**Test in your app:**

Update `app/page.tsx` to display products:

```tsx title="app/page.tsx" 
import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card";
import { getProducts } from "@/lib/sample-data";

export default function Home() {
  const products = getProducts();

  return (
    <main className="min-h-screen p-8">
      <div className="max-w-4xl mx-auto space-y-8">
        <h1 className="text-4xl font-bold">Product Reviews</h1>

        <div className="grid gap-4">
          {products.map((product) => (
            <Card key={product.slug}>
              <CardHeader>
                <CardTitle>{product.name}</CardTitle>
              </CardHeader>
              <CardContent>
                <p className="text-sm text-muted-foreground">
                  {product.description}
                </p>
                <p className="text-sm mt-2">
                  {product.reviews.length} reviews
                </p>
              </CardContent>
            </Card>
          ))}
        </div>
      </div>
    </main>
  );
}
```

**Visit <http://localhost:3000>**

You should see:

- 3 product cards
- Product names and descriptions
- Review counts

**Test type safety:**

Try accessing a non-existent field:

```typescript
product.nonExistentField // TypeScript error!
```

Try passing wrong data:

```typescript
const badReview = { stars: 10 }; // Will fail Zod validation (max is 5)
ReviewSchema.parse(badReview); // Throws error
```

## Understanding Zod Benefits

**Without Zod (plain TypeScript):**

```typescript
type Product = {
  name: string;
  reviews: Review[];
};

// Runtime: no validation
// If API returns bad data, your app breaks
```

**With Zod:**

```typescript
const ProductSchema = z.object({
  name: z.string(),
  reviews: z.array(ReviewSchema),
});

type Product = z.infer<typeof ProductSchema>;

// Runtime: validated with .parse()
// Type errors caught at compile time AND runtime
```

**Key advantages:**

1. **Single source of truth** - Schema defines both validation and types
2. **Runtime safety** - Catches invalid data from APIs, forms, databases
3. **Better errors** - Zod errors are detailed and actionable
4. **No type drift** - Types automatically match validation rules

## Project Structure

Your data layer is now:

```
lib/
├── types.ts           # Zod schemas + TypeScript types
├── sample-data.ts     # Sample products with reviews
└── utils.ts           # Helper functions (from shadcn)
```

## Done-When

- [ ] Zod installed and schemas created
- [ ] Product and Review types defined
- [ ] Sample data with 3+ products and multiple reviews
- [ ] Helper functions `getProducts()` and `getProduct()` implemented
- [ ] Homepage displays product list
- [ ] Full type safety with compile-time and runtime validation

## What's Next

Your data layer is solid and type-safe. In the next lesson, you'll build UI components to display individual reviews with star ratings, avatars, and timestamps. These components will use the Product and Review types you just created.

***

**Sources:**

- [Zod Documentation](https://zod.dev)
- [TypeScript Type Inference](https://www.typescriptlang.org/docs/handbook/type-inference.html)


---

[Full course index](/academy/llms.txt) · [Sitemap](/academy/sitemap.md)
