最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

typescript - Synchronize Supabase types with Zod to validate data in API routes - Stack Overflow

programmeradmin1浏览0评论

Context

I'm building a Next.js application with TypeScript and Supabase as my backend. Supabase CLI automatically generates TypeScript types from my database schema, which look like this:

export type Database = {
  public: {
    Tables: {
      ingredients: {
        Row: {
          calories: number | null
          carbohydrates: number | null
          created_at: string
          fiber: number | null
          id: number
          lipids: number | null
          name: string
          proteins: number | null
          saturated_fat: number | null
          sodium: number | null
          sugars: number | null
        }
        Insert: {
          calories?: number | null
          carbohydrates?: number | null
          created_at?: string
          fiber?: number | null
          id?: number
          lipids?: number | null
          name: string
          proteins?: number | null
          saturated_fat?: number | null
          sodium?: number | null
          sugars?: number | null
        }
        Update: {
          calories?: number | null
          carbohydrates?: number | null
          created_at?: string
          fiber?: number | null
          id?: number
          lipids?: number | null
          name?: string
          proteins?: number | null
          saturated_fat?: number | null
          sodium?: number | null
          sugars?: number | null
        }
      }
      // ... other tables
    }
  }
}

Problem

I want to use Zod to validate data in my API requests, but I don't want to manually maintain the schemas in sync with my Supabase types. I need a way to:

  • Automatically generate Zod schemas from the Supabase types
  • Keep the schemas in sync when the database schema changes

Current approach

Currently, I'm manually creating my Zod schemas like this:

export const ingredientSchema = z.object({
  name: z.string().min(1, "Ingredient name is required"),
  calories: z.number().nullable().optional(),
  carbohydrates: z.number().nullable().optional(),
  fiber: z.number().nullable().optional(),
  lipids: z.number().nullable().optional(),
  proteins: z.number().nullable().optional(),
  saturated_fat: z.number().nullable().optional(),
  sodium: z.number().nullable().optional(),
  sugars: z.number().nullable().optional(),
});

But this approach is:

  • Error-prone: I might miss fields when the database schema changes
  • Redundant: I'm essentially duplicating type information
  • Hard to maintain: Changes need to be made in multiple places

Question

What's the best way to automatically generate Zod schemas from Supabase-generated types while still allowing custom validation rules? Is there a utility or pattern that could help with this?

Any help or guidance would be greatly appreciated!

Context

I'm building a Next.js application with TypeScript and Supabase as my backend. Supabase CLI automatically generates TypeScript types from my database schema, which look like this:

export type Database = {
  public: {
    Tables: {
      ingredients: {
        Row: {
          calories: number | null
          carbohydrates: number | null
          created_at: string
          fiber: number | null
          id: number
          lipids: number | null
          name: string
          proteins: number | null
          saturated_fat: number | null
          sodium: number | null
          sugars: number | null
        }
        Insert: {
          calories?: number | null
          carbohydrates?: number | null
          created_at?: string
          fiber?: number | null
          id?: number
          lipids?: number | null
          name: string
          proteins?: number | null
          saturated_fat?: number | null
          sodium?: number | null
          sugars?: number | null
        }
        Update: {
          calories?: number | null
          carbohydrates?: number | null
          created_at?: string
          fiber?: number | null
          id?: number
          lipids?: number | null
          name?: string
          proteins?: number | null
          saturated_fat?: number | null
          sodium?: number | null
          sugars?: number | null
        }
      }
      // ... other tables
    }
  }
}

Problem

I want to use Zod to validate data in my API requests, but I don't want to manually maintain the schemas in sync with my Supabase types. I need a way to:

  • Automatically generate Zod schemas from the Supabase types
  • Keep the schemas in sync when the database schema changes

Current approach

Currently, I'm manually creating my Zod schemas like this:

export const ingredientSchema = z.object({
  name: z.string().min(1, "Ingredient name is required"),
  calories: z.number().nullable().optional(),
  carbohydrates: z.number().nullable().optional(),
  fiber: z.number().nullable().optional(),
  lipids: z.number().nullable().optional(),
  proteins: z.number().nullable().optional(),
  saturated_fat: z.number().nullable().optional(),
  sodium: z.number().nullable().optional(),
  sugars: z.number().nullable().optional(),
});

But this approach is:

  • Error-prone: I might miss fields when the database schema changes
  • Redundant: I'm essentially duplicating type information
  • Hard to maintain: Changes need to be made in multiple places

Question

What's the best way to automatically generate Zod schemas from Supabase-generated types while still allowing custom validation rules? Is there a utility or pattern that could help with this?

Any help or guidance would be greatly appreciated!

Share Improve this question edited Nov 20, 2024 at 23:36 Brian Tompsett - 汤莱恩 5,89372 gold badges61 silver badges133 bronze badges asked Nov 20, 2024 at 10:48 BlakyrisBlakyris 134 bronze badges 1
  • Please edit the question to limit it to a specific problem with enough detail to identify an adequate answer. – Community Bot Commented Nov 20, 2024 at 11:25
Add a comment  | 

1 Answer 1

Reset to default 0

This isn't ideal but has helped me and ensures that everything is typed which helps with the errors and maintenance. It was inspired by a thread I found when dealing with a similar challenge.

This is a helper utility to infer the Zod schema from the type. It means you get:

  • typed schema creation
  • auto-complete on available columns from a table
  • the Zod type must match the Supabase column type to be valid

The utility:

import type { z } from "zod";

export type Equals<X, Y> = (<T>() => T extends X ? 1 : 2) extends <T>() => T extends Y ? 1 : 2 ? true : false;

type NonUndefined<T> = Exclude<T, undefined>;

// Infer the Zod schema from a TypeScript type to construct a typed Zod object.
export type ZodInferSchema<T extends object> = {
    [Key in keyof T]-?: Equals<T[Key], NonUndefined<T[Key]>> extends false
        ? // biome-ignore lint/suspicious/noExplicitAny: Allowing any for flexibility
            z.ZodOptional<z.ZodType<NonNullable<T[Key]>>> | z.ZodPipeline<z.ZodOptional<z.ZodType<any>>, z.ZodType<T[Key]>>
        : // biome-ignore lint/suspicious/noExplicitAny: Allowing any for flexibility
            z.ZodType<T[Key]> | z.ZodPipeline<z.ZodType<any>, z.ZodType<T[Key]>>;
};

Using this helper in your schema definition:

import type { Tables } from "@/lib/supabase/types";
import { z } from "zod";

import type { ZodInferSchema } from "@/lib/utils/functions/type-to-zod";

type Change = Tables<"change_log">;

const change = z.object<ZodInferSchema<Change>>({
    changed_at: z.string(),
    id: z.string(),
    operation: z.string(),
    record_id: z.string(),
    site_id: z.string().nullable(),
    table_name: z.string(),
});

export const selectChangesSchema = change;

export const insertChangeSchema = change
    .extend({
        changed_at: z.string().datetime(),
        site_id: z.string(),
    })
    .partial({
        id: true,
    });

export type InsertChangeSchema = z.infer<typeof insertChangeSchema>;

I'd love to know if you find a better way!

发布评论

评论列表(0)

  1. 暂无评论