Keeping TypeScript Type Safety When You Drop the ORM
Switching from Prisma to raw MySQL2 means your query results become RowDataPacket[] — effectively any[]. Hand-writing interfaces creates two sources of truth that will drift. Using Prisma-generated types as casts on MySQL2 results gives you full type safety from a single schema source.
Dropping Prisma as the query layer meant giving up Prisma's automatic TypeScript inference. With Prisma, you write prisma.store.findMany() and get a fully typed Store[] back. With MySQL2, you get RowDataPacket[] and whatever you cast it to.
Giving up type safety was not an option. There is a pattern that keeps it.
Problem
MySQL2 returns query results as RowDataPacket[], which is effectively any[] from TypeScript's perspective. Without explicit types, every field access is untyped. You lose autocomplete, you lose compile-time checks on field names, and you can ship typos in field access that only surface at runtime.
The obvious answer is write your own interfaces for every database table. That works but it creates a maintenance problem: when the schema changes, you update schema.prisma and you also update your hand-written interfaces. Two sources of truth for the same structure. They will drift.

Solution
Keep Prisma for schema management. Let Prisma generate the TypeScript types. Use those generated types with MySQL2.
Prisma generates a TypeScript namespace from your schema.prisma file. Every model becomes a type with the exact fields defined in the schema. If you have a Store model with store_id, store_name, and region, Prisma generates a Store type with those exact fields.
In MySQL2 queries, import the Prisma-generated type and cast the RowDataPacket result to it. The cast tells TypeScript what shape to expect. Field access becomes typed. Autocomplete works. Typos in field names are compile-time errors.
For partial results where you only select a few columns, use Pick from the generated type. The base types stay Prisma-generated and schema-accurate. Your partial types derive from them.
The schema is the single source of truth. When a column is added or renamed in schema.prisma and the migration runs, the generated types update automatically on the next type generation step. No hand-written interface to find and update.

Result
Full TypeScript type coverage on MySQL2 query results without maintaining separate interface files.
When a column is renamed in the schema and types regenerate, TypeScript immediately flags every usage of the old field name across the codebase. Compile-time, not runtime.
The pattern is clear in practice: Prisma owns the schema and generates types, MySQL2 owns the query execution, TypeScript enforces correctness at the interface between them. Each tool does one thing.
Found this useful?
Share it with someone who'd appreciate it.
https://wardvisual.com/blogs/typescript-types-raw-sql-mysql2

@wardvisual · 🇵🇭 Dasmarinas City, Cavite PH
Full-stack engineer. Business systems, database optimization, and operations software.