Back to Dev
TypeScript19 Apr 202610 min read

TypeScript Patterns That Actually Ship

Three techniques that cut Vue + Laravel API bugs by 80%—no verbosity, just results.

TypeScript Vue Laravel API

TypeScript gets called verbose when people fight the type system instead of working with it. These three patterns flipped that script for me.

Discriminated Unions for API Responses

type ApiResponse<T> = 
  | { success: true; data: T }
  | { success: false; error: string }

const response: ApiResponse<User> = await fetchUser()

if (response.success) {
  // TypeScript knows response.data is User
  response.data.email
} else {
  // TypeScript knows response.error exists
  console.error(response.error)
}

No more if (response.error) guesswork. The compiler guards every branch.

Generic Composables in Vue

// composables/useFetch.ts
export const useFetch = <T>() => {
  const data = ref<T | null>(null)
  const error = ref<string | null>(null)
  
  const fetch = async (url: string) => {
    const res = await fetch(url)
    if (!res.ok) {
      error.value = 'Failed'
      return
    }
    data.value = await res.json() as T
  }
  
  return { data, error, fetch }
}

// Usage knows the exact return type
const { data } = useFetch<UserProfile>()

20 lines upfront, zero any debugging later.

Zod at the Boundary

const userSchema = z.object({
  id: z.number(),
  email: z.string().email(),
  name: z.string()
})

const response = await fetch('/api/user')
const user = userSchema.parse(await response.json())

// Everything past this point is 100% typed
user.email // string, guaranteed

Result: Last Laravel + Vue project refactor eliminated 80% of runtime type errors. The compiler became my QA team.

Ship faster. Type smarter.

More Dev Posts