Building Type-Safe APIs with Nuxt 3 and Prisma
Learn how to build end-to-end type-safe APIs using Nuxt 3 server routes and Prisma ORM for a seamless developer experience.
Practical TypeScript patterns for real codebases — discriminated unions, branded types, builder patterns, and more.
Mbeah Essilfie
July 11, 2026 at 06:03 AM
Most developers use TypeScript as a type-annotation layer. But its type system is powerful enough to encode business rules, eliminate impossible states, and guide developers toward correct code.
// Instead of optional fields that lead to impossible states:
type Result<T> =
| { status: 'success'; data: T }
| { status: 'error'; error: Error }
| { status: 'loading' }
function handleResult(result: Result<User>) {
switch (result.status) {
case 'success':
// TypeScript knows result.data exists here
console.log(result.data.name)
break
case 'error':
// TypeScript knows result.error exists here
console.error(result.error.message)
break
case 'loading':
// No data or error accessible
break
}
}
Prevent mixing up values that share the same primitive type:
type UserId = string & { __brand: 'UserId' }
type PostId = string & { __brand: 'PostId' }
function createUserId(id: string): UserId {
return id as UserId
}
function getPost(postId: PostId) { /* ... */ }
const userId = createUserId('abc-123')
// getPost(userId) // ❌ Type error! Can't pass UserId as PostId
class QueryBuilder<T> {
private filters: Record<string, unknown> = {}
private sortField?: keyof T
private limitValue?: number
where<K extends keyof T>(field: K, value: T[K]): this {
this.filters[field as string] = value
return this
}
orderBy(field: keyof T): this {
this.sortField = field
return this
}
limit(n: number): this {
this.limitValue = n
return this
}
build() {
return { filters: this.filters, sort: this.sortField, limit: this.limitValue }
}
}
// Usage - fully typed!
const query = new QueryBuilder<Post>()
.where('status', 'published')
.orderBy('publishedAt')
.limit(10)
.build()
function assertNever(x: never): never {
throw new Error(`Unexpected value: ${x}`)
}
type Theme = 'light' | 'dark' | 'system'
function getBackground(theme: Theme): string {
switch (theme) {
case 'light': return '#ffffff'
case 'dark': return '#1a1a1a'
case 'system': return 'var(--bg)'
default: return assertNever(theme) // Compile error if you miss a case
}
}
Good TypeScript makes invalid states unrepresentable. Use the type system as documentation that the compiler enforces.
Fullstack Software Developer
Learn how to build end-to-end type-safe APIs using Nuxt 3 server routes and Prisma ORM for a seamless developer experience.
Go beyond basic utility classes and learn how to build cohesive, maintainable design systems with Tailwind CSS.
Understand the power of Vue 3's Composition API through practical, real-world composable patterns that you can use today.
