DEV Community

Cover image for TypedSQL in Prisma: Type-Safe SQL Made Simple
Fahim Ahammed Firoz
Fahim Ahammed Firoz

Posted on

TypedSQL in Prisma: Type-Safe SQL Made Simple

TypedSQL is a powerful feature in Prisma that allows developers to write raw SQL queries while maintaining type safety and full integration with Prisma’s TypeScript client. This feature is especially useful for scenarios where complex SQL queries are necessary, but you still want to benefit from Prisma’s type safety, code generation, and developer experience (DX). In this blog, we'll walk through the steps to get started with TypedSQL, how to pass arguments to queries, define argument types, and some best practices.

Setting Up TypedSQL in Prisma

To start using TypedSQL in your Prisma project, follow these steps:

1. Install the Required Packages

Ensure that you have @prisma/client and prisma installed and updated to at least version 5.19.0. You can do this by running the following commands:

npm install @prisma/client@latest
npm install -D prisma@latest
Enter fullscreen mode Exit fullscreen mode

2. Enable TypedSQL in Your Schema

Add the typedSql preview feature flag to your schema.prisma file:

generator client {
  provider = "prisma-client-js"
  previewFeatures = ["typedSql"]
}
Enter fullscreen mode Exit fullscreen mode

3. Create a Directory for SQL Files

Inside your prisma directory, create a sql directory where you'll write your SQL queries:

mkdir -p prisma/sql
Enter fullscreen mode Exit fullscreen mode

4. Write Your SQL Queries

Create a new .sql file in the prisma/sql directory. The file name must be a valid JavaScript identifier and cannot start with a $. For example, let's create a getUsersWithPosts.sql file:

-- prisma/sql/getUsersWithPosts.sql
SELECT u.id, u.name, COUNT(p.id) as "postCount"
FROM "User" u
LEFT JOIN "Post" p ON u.id = p."authorId"
GROUP BY u.id, u.name
Enter fullscreen mode Exit fullscreen mode

5. Generate the Prisma Client

To generate TypeScript functions and types for your SQL queries, run the following command:

prisma generate --sql
Enter fullscreen mode Exit fullscreen mode

If you want Prisma to regenerate the client automatically after every change, you can use the --watch flag:

prisma generate --sql --watch
Enter fullscreen mode Exit fullscreen mode

6. Use TypedSQL Queries in Your Code

You can now import and use your SQL queries in your TypeScript code:

import { PrismaClient } from '@prisma/client'
import { getUsersWithPosts } from '@prisma/client/sql'

const prisma = new PrismaClient()

const usersWithPostCounts = await prisma.$queryRawTyped(getUsersWithPosts())
console.log(usersWithPostCounts)
Enter fullscreen mode Exit fullscreen mode

Passing Arguments to TypedSQL Queries

To pass arguments to your TypedSQL queries, you can use parameterized queries. This allows you to write flexible and reusable SQL statements while maintaining type safety.

Example: Passing Parameters

In your SQL file, use placeholders for the parameters you want to pass. The syntax for placeholders depends on your database engine. For PostgreSQL, use the positional placeholders $1, $2, etc.:

-- prisma/sql/getUsersByAge.sql
SELECT id, name, age
FROM users
WHERE age > $1 AND age < $2
Enter fullscreen mode Exit fullscreen mode

When using the generated function in your TypeScript code, pass the arguments as additional parameters to $queryRawTyped:

import { PrismaClient } from '@prisma/client'
import { getUsersByAge } from '@prisma/client/sql'

const prisma = new PrismaClient()

const minAge = 18
const maxAge = 30
const users = await prisma.$queryRawTyped(getUsersByAge(minAge, maxAge))
console.log(users)
Enter fullscreen mode Exit fullscreen mode

Example: Passing Array Arguments

TypedSQL supports passing arrays as arguments for PostgreSQL. Use PostgreSQL's ANY operator with an array parameter:

-- prisma/sql/getUsersByIds.sql
SELECT id, name, email
FROM users
WHERE id = ANY($1)
Enter fullscreen mode Exit fullscreen mode

In your TypeScript code:

import { PrismaClient } from '@prisma/client'
import { getUsersByIds } from '@prisma/client/sql'

const prisma = new PrismaClient()

const userIds = [1, 2, 3]
const users = await prisma.$queryRawTyped(getUsersByIds(userIds))
console.log(users)
Enter fullscreen mode Exit fullscreen mode

Defining Argument Types in SQL Files

You can define the types of arguments directly in your SQL files using specific comments. This enhances type safety and ensures that the TypeScript client is fully aware of the types being passed.

Example: Typing Arguments

Add the following comments to define the types:

-- @param {Int} $1:minAge The minimum age of the user
-- @param {Int} $2:maxAge The maximum age of the user
SELECT id, name, age
FROM users
WHERE age > $1 AND age < $2
Enter fullscreen mode Exit fullscreen mode

The format is consistent across all supported database engines and ensures that your TypeScript code accurately reflects the SQL query structure.

Pros and Cons of Using TypedSQL

Pros

  1. Type Safety: TypedSQL provides strong type safety for raw SQL queries, reducing the risk of runtime errors and ensuring consistent data handling.

  2. Integrated DX: By integrating with Prisma, TypedSQL allows you to write complex SQL queries while still benefiting from Prisma’s developer tools, like autocomplete and type inference.

  3. Reusable Queries: TypedSQL encourages the reuse of SQL queries across your application, making it easier to maintain and update your codebase.

  4. Protection Against SQL Injection: By using parameterized queries, TypedSQL helps protect your application against SQL injection attacks, which are a common security vulnerability.

  5. Better Performance for Complex Queries: TypedSQL allows you to execute more complex and efficient SQL queries directly within Prisma, bypassing some of the limitations of Prisma’s query builder.

Cons

  1. Limited Dynamic SQL Support: TypedSQL does not natively support constructing SQL queries with dynamically added columns. For such cases, you need to fall back to $queryRaw or $executeRaw, which means losing type safety.

  2. Increased Complexity: Using raw SQL, even with type safety, adds a layer of complexity to your codebase, especially if your team is more familiar with Prisma’s query builder syntax.

  3. Learning Curve: Developers need to be comfortable with both SQL and Prisma’s TypeScript client to use TypedSQL effectively. This might require additional learning for some team members.

  4. Dependency on Preview Features: TypedSQL is currently a preview feature, meaning it’s not yet fully stable and might undergo breaking changes in future releases.

  5. Lack of Full ORM Benefits: When using TypedSQL, you miss out on some of the abstractions and conveniences that Prisma’s ORM provides, such as automatic relation handling and query optimization.

Conclusion

TypedSQL is a powerful tool in Prisma’s ecosystem that bridges the gap between raw SQL and type-safe query construction. It offers the flexibility of raw SQL with the benefits of Prisma’s TypeScript client, making it ideal for scenarios where you need fine-grained control over your database queries.

However, it’s important to weigh the pros and cons before adopting TypedSQL in your project. While it offers many advantages, particularly in terms of type safety and query performance, it also introduces complexity and requires a good understanding of SQL.

By following the steps outlined in this guide, you can start using TypedSQL in your Prisma projects, writing more complex queries while maintaining the benefits of Prisma’s type-safe client.

Top comments (0)