TypeScript types from Rails serializers

Maya Patel Jan 2026
1 tab
export type PostId = string & { readonly brand: unique symbol }
export type UserId = string & { readonly brand: unique symbol }

export interface User {
  id: UserId
  name: string
  email: string
  avatar_url: string | null
}

export type PostStatus = 'draft' | 'published' | 'archived'

export interface Post {
  id: PostId
  title: string
  body: string
  excerpt: string
  status: PostStatus
  published_at: string | null
  created_at: string
  updated_at: string
  author: User
  tags: string[]
  comments_count: number
  likes_count: number
}

export interface PostFormData {
  title: string
  body: string
  status: PostStatus
  tags: string[]
}

export interface PaginatedResponse<T> {
  data: T[]
  meta: {
    current_page: number
    total_pages: number
    total_count: number
    per_page: number
  }
}
1 file · typescript Explain with highlit

Keeping TypeScript types in sync with Rails API responses is critical but tedious. I generate TypeScript interfaces automatically from Rails serializers or JSON Schema using tools like quicktype or custom scripts. For manual definitions, I create a types directory that mirrors the Rails model structure. Each type includes all attributes from the serializer plus computed fields. I use branded types for IDs to prevent mixing up user IDs with post IDs. Unions and literal types represent Rails enums. The key discipline is updating types whenever the API shape changes—I catch this in code review and with integration tests that verify response shapes. Proper types catch API contract violations at compile time instead of runtime.