Syncing Database Enums with TypeScript: A Supabase Postgres Types Approach

When building applications with TypeScript and Postgres, maintaining consistency between your database schema and your application types can be challenging, especially when dealing with enums. In this post, we’ll explore an efficient approach to keep your TypeScript enums in sync with your Supabase PostgreSQL database enums.

The Challenge

Imagine you have an enum in your PostgreSQL database:

CREATE TYPE workflow_type AS ENUM (
'no_doc',
'iterative',
'knowledge_base',
'interactive',
'composite'
);

In your TypeScript code, you might be tempted to define a corresponding enum:

export enum WorkflowType {
NO_DOC = "no_doc",
ITERATIVE = "iterative",
KNOWLEDGE_BASE = "knowledge_base",
INTERACTIVE = "interactive",
COMPOSITE = "composite",
}

However, this approach introduces a potential source of inconsistency. If the database enum is updated, you need to remember to update your TypeScript enum manually.

The Supabase Postgres Solution

Supabase provides a powerful feature that can help us solve this problem: auto-generated TypeScript types based on your database schema.

Step 1: Generate Types

First, ensure you’ve generated your Supabase types. This is typically done using the Supabase CLI:

supabase gen types typescript --project-id your-project-id > types/supabase.ts

Step 2: Leverage Generated Types

In your supabase.ts file, you’ll find a comprehensive type definition for your database, including enums:

export type Database = {
public: {
Tables: { /* ... */ };
Views: { /* ... */ };
Functions: { /* ... */ };
Enums: {
workflow_type: "no_doc" | "iterative" | "knowledge_base" | "interactive" | "composite";
// ... other enums ...
};
};
};

Step 3: Use Enum Types in Your Application

Instead of defining your own enum, you can now use the auto-generated type:

// types/workflow.ts
import { Enums } from "./supabase";

export type WorkflowType = Enums<"workflow_type">;

This WorkflowType type will always reflect the current state of your database enum.

Benefits

  1. Single Source of Truth: Your database becomes the single source of truth for enum definitions.
  2. Automatic Synchronization: As you update your database schema and regenerate types, your TypeScript types automatically stay in sync.
  3. Type Safety: You get full TypeScript type checking and autocompletion based on the actual database schema.

Considerations

  • Remember to regenerate your Supabase types whenever you make changes to your database schema.
  • This approach works best when you have control over both the database schema and the TypeScript codebase.

Advanced Usage: Enum-like Objects with Text Mapping

While the above solution provides type safety and synchronization, some developers might prefer to work with enum-like objects that allow for additional features like text mapping. For these cases, we can use the ts-enum-util library to create a more flexible solution.

Step 1: Install ts-enum-util First, install the `ts-enum-util` library:

pnpm install ts-enum-util

Step 2: Create Enum-like Objects Now, you can create enum-like objects that maintain type safety while allowing for additional functionality:

// types/workflow.ts
import { Enums } from "./supabase";
import { EnumType } from 'ts-enum-util';

export type WorkflowType = Enums<"workflow_type">;

export const WorkflowType = new EnumType<WorkflowType>({
NO_DOC: 'no_doc',
ITERATIVE: 'iterative',
KNOWLEDGE_BASE: 'knowledge_base',
INTERACTIVE: 'interactive',
COMPOSITE: 'composite',
});

// Optional: Define a text mapping
export const WorkflowTypeText: Record<WorkflowType, string> = {
[WorkflowType.NO_DOC]: "Research",
[WorkflowType.ITERATIVE]: "Iterative Extraction",
[WorkflowType.KNOWLEDGE_BASE]: "Knowledge Base",
[WorkflowType.INTERACTIVE]: "Interactive",
[WorkflowType.COMPOSITE]: "Composite",
};

Step 3: Use in Your Application You can now use this enum-like object in your application, benefiting from both type safety and additional functionality:

import { WorkflowType, WorkflowTypeText } from './types/workflow';

function processWorkflow(type: WorkflowType) {
console.log(`Processing workflow: ${WorkflowTypeText[type]}`);
// ... rest of the function
}

processWorkflow(WorkflowType.ITERATIVE);
// Output: Processing workflow: Iterative Extraction

Benefits of this Approach

  • Maintains type safety and synchronization with the database.
  • Allows for enum-like usage (e.g., WorkflowType.ITERATIVE).
  • Provides the ability to add custom mappings (like WorkflowTypeText).
  • Easy to replicate for other enums from Supabase.

Considerations

Slightly more setup compared to the basic type approach.

Requires an additional library dependency.

Conclusion

By leveraging Supabase’s auto-generated types, we can maintain a single source of truth for our enum definitions, reducing the risk of inconsistencies between our database and application code. This approach not only saves time but also enhances type safety and maintainability in our TypeScript projects.

Remember, good type management is key to building robust and maintainable applications, especially when working with databases. The technique described here is just one of many ways Supabase helps streamline the development process and maintain consistency across your stack.