Menu

How to Convert SQL CREATE TABLE to TypeScript, Pydantic, and Go Types

Convert database CREATE TABLE DDL statements into TypeScript interfaces, Python Pydantic models, and Go structs with type mappings, nullability rules, and in-browser conversion.

Posted on By
On this page

When developing modern web APIs, backend services, or frontend applications, keeping your application code types in sync with your relational database schema is essential. Manually recreating TypeScript interfaces, Python Pydantic models, or Go structs from SQL CREATE TABLE definitions is repetitive, time-consuming, and a frequent source of subtle runtime bugs—especially around nullability, timestamps, and numeric precisions.

You can convert any SQL schema into strong programming language types instantly using the free browser-based SQL to Types & Models Converter.

Below is a practical guide explaining how relational SQL data types map to TypeScript, Python Pydantic, and Go, along with common pitfalls to avoid.


Example SQL Schema

Consider a typical production e-commerce user and order schema in PostgreSQL:

CREATE TABLE users (
    id UUID PRIMARY KEY,
    email VARCHAR(255) NOT NULL UNIQUE,
    full_name VARCHAR(128) NOT NULL,
    is_active BOOLEAN NOT NULL DEFAULT true,
    role VARCHAR(32) NOT NULL DEFAULT 'customer',
    tags TEXT[] NOT NULL DEFAULT '{}',
    metadata JSONB,
    created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
    last_login_at TIMESTAMPTZ
);

When building an API that queries this table, each language ecosystem represents these fields with different idioms:

Generated TypeScript Interface

In TypeScript, database rows are commonly represented as interfaces with optional or nullable types and camelCase property names:

export interface User {
  /** SQL: UUID | PRIMARY KEY */
  id: string;
  /** SQL: VARCHAR(255) */
  email: string;
  /** SQL: VARCHAR(128) */
  fullName: string;
  /** SQL: BOOLEAN | DEFAULT: true */
  isActive: boolean;
  /** SQL: VARCHAR(32) | DEFAULT: 'customer' */
  role: string;
  /** SQL: TEXT[] | DEFAULT: '{}' */
  tags: string[];
  /** SQL: JSONB */
  metadata: Record<string, unknown> | null;
  /** SQL: TIMESTAMPTZ | DEFAULT: CURRENT_TIMESTAMP */
  createdAt: string;
  /** SQL: TIMESTAMPTZ */
  lastLoginAt: string | null;
}

Generated Python Pydantic v2 Model

In Python backends (such as FastAPI), schemas are validated using Pydantic models with explicit optional types and default fallbacks:

from datetime import datetime
from typing import Any
from uuid import UUID
from pydantic import BaseModel, Field


class User(BaseModel):
    id: UUID  # SQL: UUID, PK
    email: str  # SQL: VARCHAR(255)
    full_name: str  # SQL: VARCHAR(128)
    is_active: bool = True  # SQL: BOOLEAN, default=true
    role: str = 'customer'  # SQL: VARCHAR(32), default='customer'
    tags: list[str] = Field(default_factory=list)  # SQL: TEXT[]
    metadata: dict[str, Any] | None = None  # SQL: JSONB
    created_at: datetime  # SQL: TIMESTAMPTZ, default=CURRENT_TIMESTAMP
    last_login_at: datetime | None = None  # SQL: TIMESTAMPTZ

Generated Go Struct

In Go, field names must be capitalized (PascalCase) to be exported across packages, while struct tags specify the serialized JSON and database column names:

package models

import (
	"time"
)

type User struct {
	ID          string         `json:"id" db:"id"`                 // UUID (PK)
	Email       string         `json:"email" db:"email"`           // VARCHAR(255)
	FullName    string         `json:"full_name" db:"full_name"`   // VARCHAR(128)
	IsActive    bool           `json:"is_active" db:"is_active"`   // BOOLEAN
	Role        string         `json:"role" db:"role"`             // VARCHAR(32)
	Tags        []string       `json:"tags" db:"tags"`             // TEXT[]
	Metadata    map[string]any `json:"metadata" db:"metadata"`     // JSONB
	CreatedAt   time.Time      `json:"created_at" db:"created_at"` // TIMESTAMPTZ
	LastLoginAt *time.Time     `json:"last_login_at" db:"last_login_at"` // TIMESTAMPTZ
}

Core Challenges in Mapping SQL Schemas to Code

1. Nullable Columns vs Optional Properties

In SQL, every column without an explicit NOT NULL constraint is nullable by default. However, programming languages treat “can be null” and “can be missing” differently:

  • SQL NULL: The column exists in the row, but its value is empty or unknown.
  • TypeScript null vs undefined: A database result typically contains { last_login_at: null }, not {}. Therefore, typing the field as string | null matches database driver output (such as pg, mysql2, or SQLite) better than last_login_at?: string.
  • Go pointers: Go primitive types cannot be nil. To represent nullable columns, use pointer types (e.g. *string, *time.Time, *int64) or standard library wrappers like sql.NullString.
  • Python None: In Python 3.10+, union syntax type | None = None cleanly expresses nullable fields.

2. Timestamps and Date Strings

Relational databases store timestamps with microsecond precision and timezone offsets. In application runtimes:

  • When fetched over HTTP or JSON APIs, dates are almost always serialized as ISO 8601 strings (e.g. "2026-10-11T08:30:00Z").
  • In TypeScript frontend applications, typing timestamps as string avoids unintended timezone mutations. When using an ORM like Prisma or TypeORM, typing them as Date may be preferred.
  • In Python, datetime.datetime is the standard type, and Pydantic automatically serializes and deserializes ISO strings.

3. Financial Decimals and Floating-Point Precision

SQL types like DECIMAL(12, 2) or NUMERIC(18, 4) are exact fixed-point numbers used for monetary values.

  • JavaScript and TypeScript only have the IEEE-754 double-precision number type, which can introduce rounding errors on operations like 0.1 + 0.2. For financial applications, string representations or specialized libraries (such as decimal.js or bignumber.js) are recommended.
  • In Python, always map DECIMAL / NUMERIC to decimal.Decimal to preserve exact precision.
  • In Go, map to float64 or a decimal package like shopspring/decimal.

4. Semi-Structured JSON Columns

Modern databases frequently mix relational and document paradigms via JSON (MySQL, SQLite) and JSONB (PostgreSQL):

  • TypeScript: Use Record<string, unknown> rather than any to enforce type narrowing before property access.
  • Python: Use dict[str, Any].
  • Go: Use map[string]any or json.RawMessage when deferring deserialization.

Data Type Comparison Matrix

Database Type PostgreSQL / MySQL / SQLite TypeScript Python (Pydantic) Go Struct
32-bit Integer INT, INTEGER, SERIAL number int int
64-bit Integer BIGINT, BIGSERIAL number int int64
Fixed Decimal DECIMAL(p, s), NUMERIC number (or string) Decimal float64
Floating Point FLOAT, DOUBLE, REAL number float float64
Character String VARCHAR(n), TEXT string str string
Boolean Flag BOOLEAN, TINYINT(1), BIT boolean bool bool
Timestamp with TZ TIMESTAMPTZ, DATETIME string (or Date) datetime time.Time
Calendar Date DATE string (or Date) date time.Time
Semi-Structured JSON, JSONB Record<string, unknown> dict[str, Any] map[string]any
UUID UUID, UNIQUEIDENTIFIER string UUID string
Enumeration ENUM('active', 'inactive') 'active' | 'inactive' str string
PostgreSQL Array TEXT[], INT[] string[], number[] list[str], list[int] []string, []int

Converting Schemas Online

To convert your database tables without installing CLI packages or command-line generators:

  1. Open the SQL to Types Converter.
  2. Select your target language format: TypeScript Interface, TypeScript Type, Python Pydantic v2, Python Dataclass, Go Struct, Rust Struct, or JSON Schema.
  3. Choose your property naming preference: camelCase, snake_case, or preserve exact SQL names.
  4. Paste your CREATE TABLE DDL statement into the editor.
  5. Click Convert to Types to inspect and copy your generated code.

You can also click Open DDL in SQLite Playground to verify table creation and execute queries in the browser using the SQLite Playground, or align syntax formatting with the SQL Formatter.