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.
Finally understand TypeScript generics. From simple type parameters to conditional types and mapped types with real-world examples.
Mbeah Essilfie
June 5, 2026 at 06:03 AM
Generics let you write code that works with any type while still maintaining type safety. Without them, you'd either lose type information or duplicate code for every type.
// Without generics — loses type info
function first(arr: any[]): any {
return arr[0]
}
// With generics — preserves type
function first<T>(arr: T[]): T | undefined {
return arr[0]
}
const num = first([1, 2, 3]) // type: number
const str = first(['a', 'b']) // type: string
Limit what types are accepted:
interface HasId {
id: string
}
function findById<T extends HasId>(items: T[], id: string): T | undefined {
return items.find(item => item.id === id)
}
// Works with any object that has an id
findById(users, '123') // returns User | undefined
findById(posts, '456') // returns Post | undefined
function merge<A, B>(objA: A, objB: B): A & B {
return { ...objA, ...objB }
}
const result = merge({ name: 'Mbeah' }, { role: 'developer' })
// type: { name: string } & { role: string }
type EventMap = {
'user:login': { userId: string; timestamp: number }
'user:logout': { userId: string }
'post:created': { postId: string; title: string }
}
class TypedEmitter<Events extends Record<string, any>> {
private handlers = new Map<string, Function[]>()
on<K extends keyof Events>(event: K, handler: (payload: Events[K]) => void) {
const list = this.handlers.get(event as string) || []
list.push(handler)
this.handlers.set(event as string, list)
}
emit<K extends keyof Events>(event: K, payload: Events[K]) {
const list = this.handlers.get(event as string) || []
list.forEach(fn => fn(payload))
}
}
const emitter = new TypedEmitter<EventMap>()
emitter.on('user:login', (payload) => {
// payload is typed as { userId: string; timestamp: number }
console.log(payload.userId)
})
// emitter.emit('user:login', { userId: '123' })
// ❌ Error: missing 'timestamp'
keyof ensure that event names and their payloads always match. Impossible to emit the wrong data shape.type IsArray<T> = T extends any[] ? true : false
type A = IsArray<string[]> // true
type B = IsArray<number> // false
// Practical: extract promise value
type Unwrap<T> = T extends Promise<infer U> ? U : T
type X = Unwrap<Promise<string>> // string
type Y = Unwrap<number> // number
// Make all properties optional
type Partial<T> = {
[K in keyof T]?: T[K]
}
// Make all properties readonly
type Readonly<T> = {
readonly [K in keyof T]: T[K]
}
// Pick specific properties
type ApiResponse<T> = {
[K in keyof T as `get${Capitalize<K & string>}`]: () => T[K]
}
type UserApi = ApiResponse<{ name: string; age: number }>
// { getName: () => string; getAge: () => number }
Think of generics as functions for types. Just as regular functions take values and return values, generic types take types and return types.
Start simple, add constraints when needed, and let TypeScript's inference do the heavy lifting.
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.
