Prisma Client Extensions Examples

repository·main·Indexed 42 days ago

https://github.com/prisma/prisma-client-extensions

A collection of example implementations for Prisma Client extensions demonstrating how to add custom logic, validation, transformations, and utility methods. Includes examples for audit log context with Postgres triggers, callback-free interactive transactions, computed fields, an 'exists' function, input transformation for query restriction, runtime input validation, and Active Record-like instance methods.

Tokens
11.4K
Snippets
30
Records
46
Agent score
52%

What's inside Prisma Client Extensions

  1. Use the Readonly Client extension

    main

    The Readonly Client extension creates a Prisma Client instance that restricts operations to read-only methods (e.g., findMany, count). It prevents write operations (e.g., create, update) by throwing errors at both runtime and compile time via TypeScript.

    Note: This extension is provided as an example only and is not intended for production use. It is built using the query extension mechanism.

    // Conceptual usage: The extension restricts the client to read operations
    // findMany and count will work
    // create and update will fail at compile time and runtime
  2. Explore Prisma Client Extension examples

    main

    This repository serves as a collection of practical examples for Prisma Client extensions. These examples demonstrate various ways to augment the Prisma Client, such as adding computed fields, validating inputs, or implementing row-level security.

    Warning: The extensions provided in this repository are for demonstration purposes only and are not intended for production use. They are provided without warranty.

  3. Add static methods to Prisma models using extensions

    main

    You can create a Prisma Client extension to add custom methods to a specific model, similar to static class methods. This is useful for abstracting common query logic or creating repository-like interfaces. For example, you can add methods like signUp() or findManyByDomain() directly to a User model within the Prisma Client.

    Note: This specific implementation is provided as an example and should be adapted for production use following the official model extensions documentation.

    # This is a conceptual example of what the extension achieves:
    
    // The extension adds these methods to the User model
    await prisma.user.signUp({ ... });
    const users = await prisma.user.findManyByDomain('example.com');
  4. Add Active Record-like instance methods to Prisma models

    main

    You can use a result extension to add custom methods (like save or delete) directly to the objects returned by Prisma Client. This allows you to implement an Active Record-like pattern where model instances carry their own behavior, rather than relying solely on the client to perform operations on them.

    // Conceptual usage of the pattern demonstrated in this extension
    const user = await prisma.user.findUnique({ where: { id: 1 } });
    
    // The extension adds these methods to the returned object
    await user.save();
    await user.delete();
  5. Use JSON Field Types extension for static and runtime type safety

    main

    The json-field-types extension demonstrates how to use a Prisma Client extension to provide both static TypeScript types and runtime validation for Json fields using Zod.

    It achieves this through two extension parts:

    1. result extension: Adds a computed field (e.g., profileData) to query results. This field uses a Zod parser to parse the untyped Json field at runtime. TypeScript infers the static type from the Zod parser, ensuring query results have both static and runtime type safety.
    2. query extension: Parses the Json field of input data for write methods (like create and update) to ensure incoming data matches the expected schema.

    Caveat: Query extensions do not currently work for nested operations. Validations implemented via query extensions only run on the top-level data object passed to methods like prisma.user.create() and do not automatically trigger for nested writes.

  6. Understand the Obfuscated Fields extension concept

    main

    The obfuscated-fields extension is an example of a Prisma Client result extension. It is designed to hide sensitive data (like a password field on a User model) by ensuring the column is excluded from the underlying SQL queries. When the field is accessed on a returned object, it resolves to undefined or a placeholder string (e.g., "********").

    Warning: This extension is provided for demonstration purposes only and is not intended for production use.

  7. Implement Row Level Security (RLS) in Postgres for Multi-tenancy

    main

    To isolate data between tenants in a shared database using Postgres Row Level Security, follow these core steps:

    1. Database User: Connect to the database using a user with limited permissions that do not have the BYPASSRLS attribute. Superusers and roles with BYPASSRLS always bypass security policies.
    2. Enable RLS: Enable RLS on specific tables using SQL. It is recommended to use FORCE ROW LEVEL SECURITY to ensure policies apply even to the table owner.
    3. Define Policies: Create policies that use Postgres runtime parameters (e.g., current_setting('app.current_company_id')) to filter row access based on the current tenant.
    4. Bypass RLS (Optional): Create permissive policies that allow access to all rows if a specific runtime parameter (e.g., app.bypass_rls = 'on') is set.
    5. Automate Column Defaults (Optional): Use dbgenerated() in your Prisma schema to automatically populate tenant columns (like companyId) from the same runtime parameter used in your policies.
    -- Enable RLS
    ALTER TABLE "User" ENABLE ROW LEVEL SECURITY;
    ALTER TABLE "User" FORCE ROW LEVEL SECURITY;
    
    -- Create isolation policy
    CREATE POLICY tenant_isolation_policy ON "User" 
    USING ("companyId" = current_setting('app.current_company_id', TRUE)::uuid);
    
    -- Create optional bypass policy
    CREATE POLICY bypass_rls_policy ON "User" 
    USING (current_setting('app.bypass_rls', TRUE)::text = 'on');
  8. Use Input Transformation to restrict queries

    main

    The Input Transformation extension allows you to modify the arguments passed to Prisma Client methods (such as where, take, and skip) without affecting the return type.

    In this specific example, an extended publicPrisma client is created that automatically restricts queries to only include posts where published is true. This is achieved using a query extension.

    Important Limitations:

    • No Nested Operations: Query extensions do not currently work for nested operations. The extension will filter top-level methods (e.g., publicPrisma.post.findMany), but it will not filter posts that are included as relations of another model via include or select (nested reads).
    • Preview Flag Requirement: This example requires the extendedWhereUnique preview flag to allow filtering on non-unique fields (like published) within methods like findUnique.
  9. What are computed fields in Prisma Client Extensions

    main

    Computed fields are virtual fields added to a Prisma model via a Client Extension. They are not stored in the database but are computed at runtime.

    Key characteristics:

    • Type-safe: They integrate with TypeScript for safe access.
    • Flexible return types: They can return simple values, complex objects, or functions that act as methods on your models.
    • Dependency-aware: Computed fields must specify which other fields they depend on.
    • Composable: They can be composed or reused by other computed fields.
  10. Understand the Model Filters extension concept

    main
    The Model Filters extension is a pattern for adding reusable, composable filters to a Prisma model. Instead of writing complex, frequently used where conditions repeatedly in your application code, you can define them once within a model extension. These filters can then be accessed through the extended Prisma Client instance and passed directly into query methods like findMany, findUnique, or count.
  11. Perform custom runtime validations with Input Validation extension

    main

    The Input Validation extension is an example implementation that uses Prisma Client query extensions to perform custom runtime validations during database mutations (such as create and update). This technique allows you to sanitize user input or deny mutations that do not meet specific criteria before they reach the database.

    Important Caveats

    • No Nested Write Support: Query extensions do not currently work for nested operations. Validations are only applied to the top-level data object passed to methods like prisma.model.create(). They do not automatically trigger for nested writes.
    • Example Only: This specific implementation is provided as an example and is not intended for production use.
  12. Set RLS runtime parameters using Prisma Client extensions

    main

    To ensure RLS parameters are applied correctly in a multi-tenant environment, you must wrap queries in a transaction. This ensures the LOCAL setting (set via set_config) persists for all queries in that request but does not leak to other connections in the pool.

    An extension can automate this by providing a method that starts a transaction and executes SELECT set_config('app.current_company_id', ${companyId}, TRUE) before running the user's queries.

    Caveat: Because this extension wraps every query in a new batch transaction, explicitly running transactions with prisma.$transaction() may not work as intended in the current implementation.

    // Example of setting the parameter manually in a transaction
    await prisma.$transaction(async (tx) => {
      await tx.$executeRaw`SELECT set_config('app.current_company_id', ${companyId}, TRUE)`;
      const projects = await tx.project.findMany();
    });
    
    // Example of using an extension that automates this
    const companyPrisma = prisma.forCompany(companyId);
    const projects = await companyPrisma.project.findMany();