Skip to content

Prisma Middleware — Intercept and Modify Database Operations

DodaTech Updated 2026-06-28 6 min read

In this tutorial, you will learn about Prisma Middleware. We cover key concepts, practical examples, and best practices to help you master this topic.

Prisma middleware lets you intercept and modify database operations before they execute or after they complete, enabling cross-cutting concerns like logging, soft deletes, Caching, and access control.

What You'll Learn

By the end of this lesson you will add middleware to intercept query operations, modify query parameters and results, implement soft deletes, add audit logging, and enforce access control at the middleware level.

Why It Matters

Middleware allows you to implement cross-cutting concerns without modifying every Repository or service. A single middleware can enforce soft deletes, log slow queries, or check permissions across the entire application.

Real-World Use

DodaZIP uses middleware for soft deletes (intercepting delete operations and setting deletedAt instead) and for audit logging (recording all create/update/delete operations with user context).

Creating Middleware

Add middleware to the Prisma Client.

import { PrismaClient } from '@prisma/client';

const prisma = new PrismaClient();

// Simple logging middleware
prisma.$use(async (params, next) => {
  const before = Date.now();
  
  // Execute the original operation
  const result = await next(params);
  
  const after = Date.now();
  const duration = after - before;
  
  console.log(`Query: ${params.model}.${params.action}`);
  console.log(`Duration: ${duration}ms`);
  
  return result;
});
# middleware_setup.py
# Middleware structure

def middleware_structure():
    print("Middleware Structure:")
    print()
    print("prisma.$use(async (params, next) => {")
    print("  // Before: params contains operation details")
    print("  // - params.model: model name (e.g., 'User')")
    print("  // - params.action: action name (e.g., 'create')")
    print("  // - params.args: the query arguments")
    print("  // - params.dataPath: data path for nested queries")
    print()
    print("  const result = await next(params)")
    print()
    print("  // After: result contains the query result")
    print()
    print("  return result")
    print("})")
    print()
    print("Multiple middleware executes in order:")
    print("  Order: first registered = first to run")

middleware_structure()

Intercepting Operations

Modify query parameters before execution.

// Soft delete middleware
prisma.$use(async (params, next) => {
  // Intercept delete and deleteMany
  if (params.action === 'delete') {
    // Change to update with deletedAt
    params.action = 'update';
    params.args.data = { deletedAt: new Date() };
  }
  
  if (params.action === 'deleteMany') {
    params.action = 'updateMany';
    if (params.args.data) {
      params.args.data.deletedAt = new Date();
    } else {
      params.args.data = { deletedAt: new Date() };
    }
  }
  
  // Intercept findMany/findFirst to exclude soft-deleted
  if (params.action === 'findMany' || params.action === 'findFirst') {
    if (!params.args) params.args = {};
    if (!params.args.where) params.args.where = {};
    params.args.where.deletedAt = null;
  }
  
  return next(params);
});

// Usage: prisma.user.delete({ where: { id: 1 } })
// Actually performs: prisma.user.update({ 
//   where: { id: 1 }, 
//   data: { deletedAt: new Date() } 
// })
# soft_delete.py
# Soft delete middleware pattern

def soft_delete_pattern():
    print("Soft Delete Middleware Pattern:")
    print()
    print("Intercepted actions:")
    print("  delete   -> update with deletedAt")
    print("  deleteMany -> updateMany with deletedAt")
    print("  findMany -> add where: { deletedAt: null }")
    print("  findFirst -> add where: { deletedAt: null }")
    print()
    print("Params modification:")
    print("  params.action = 'update'")
    print("  params.args.data = { deletedAt: new Date() }")
    print()
    print("Benefits:")
    print("  - Transparent to application code")
    print("  - No changes needed in services/repos")
    print("  - Can be enabled/disabled per model")

soft_delete_pattern()

Audit Logging Middleware

Record all data changes for Compliance.

// Audit logging middleware
prisma.$use(async (params, next) => {
  const result = await next(params);
  
  // Log mutations (create, update, delete)
  const mutationActions = ['create', 'update', 'delete', 'updateMany', 'deleteMany', 'createMany'];
  
  if (mutationActions.includes(params.action)) {
    const auditEntry = {
      model: params.model,
      action: params.action,
      timestamp: new Date().toISOString(),
      args: JSON.stringify(params.args),
      userId: getCurrentUserId(), // From context
    };
    
    // Store audit log in database or external service
    await prisma.auditLog.create({ data: auditEntry });
  }
  
  return result;
});
# audit_logging.py
# Audit logging middleware

def audit_middleware():
    print("Audit Logging Middleware:")
    print()
    print("Records for every mutation:")
    print("  - model: 'User'")
    print("  - action: 'create'")
    print("  - args: { data: { ... } }")
    print("  - userId: 'user_123'")
    print("  - timestamp: '2026-06-28T...'")
    print()
    print("Use cases:")
    print("  - Compliance requirements (SOC 2, HIPAA)")
    print("  - Debugging data changes")
    print("  - Security investigations")
    print("  - User activity history")

audit_middleware()

Access Control Middleware

Enforce permissions at the query level.

// Access control middleware
prisma.$use(async (params, next) => {
  const user = getCurrentUser();
  
  // Only admins can delete
  if (params.action === 'delete' || params.action === 'deleteMany') {
    if (!user || user.role !== 'ADMIN') {
      throw new Error('Only admins can delete records');
    }
  }
  
  // Automatically filter by organization
  if (user) {
    const orgScopedModels = ['File', 'Post', 'Project'];
    
    if (orgScopedModels.includes(params.model)) {
      if (!params.args) params.args = {};
      if (!params.args.where) params.args.where = {};
      params.args.where.organizationId = user.organizationId;
    }
  }
  
  return next(params);
});
# access_control.py
# Access control middleware patterns

def access_control():
    print("Access Control Middleware Patterns:")
    print()
    print("1. Role-based filtering:")
    print("   - Admin: full access")
    print("   - User: own data only")
    print("   - Viewer: read only")
    print()
    print("2. Organization scoping:")
    print("   - Automatically add org filter")
    print("   - Prevents cross-org data leaks")
    print()
    print("3. Field-level masking:")
    print("   - Hide sensitive fields from certain roles")
    print("   - Filter params.select to remove fields")
    print()
    print("4. Rate limiting:")
    print("   - Throttle expensive operations")

access_control()

Common Mistakes

  1. Not calling next(params): Middleware must call next(params) to continue the chain. Without it, the query never executes.

  2. Modifying params incorrectly: Directly modifying params works, but be careful with nested objects. Create deep copies when needed.

  3. Performance impact of middleware: Each middleware adds overhead. Keep middleware lightweight and avoid database calls inside middleware.

  4. Middleware ordering issues: Multiple middleware run in registration order. The first registered runs first. Plan middleware ordering carefully.

  5. Not handling async errors: Middleware that throws an error prevents the query from executing. Use try/catch for expected failures.

Practice Questions

  1. What is Prisma middleware used for? Intercepting and modifying database operations for cross-cutting concerns like logging, soft deletes, and access control.

  2. How do you register middleware? Call prisma.$use(callback) before any queries. Multiple middleware can be registered.

  3. How do you soft delete using middleware? Intercept delete/deleteMany actions, change them to update/updateMany with deletedAt timestamp.

  4. What should every middleware return? It must return the result of next(params) to continue the chain.

  5. Challenge: Create middleware that implements soft deletes, audit logging for mutations, and automatic organization scoping for all queries.

FAQ

Can middleware modify results?

Yes. After calling next(params), you can modify the result before returning it.

Does middleware work with $transaction?

Yes. Middleware executes for each operation within a transaction.

Can I skip middleware for specific queries?

Not directly. Use conditionals within the middleware to bypass certain operations.

What is the performance impact of middleware?

Minimal for simple middleware. Complex middleware (especially with DB calls) adds latency.

Can I use middleware with raw queries?

No. Middleware only intercepts Prisma Client queries, not $queryRaw or $executeRaw.

Mini Project

Create a middleware stack for a production application: soft delete middleware, audit logging middleware, organization scoping middleware, and slow query logging middleware.

def middleware_stack():
    print("Production Middleware Stack:")
    print()
    print("Order matters (first registered = outer):")
    print()
    print("1. Organization Scoping (outermost)")
    print("   - Adds organizationId filter for all queries")
    print()
    print("2. Audit Logging")
    print("   - Logs all mutations to audit_log table")
    print()
    print("3. Soft Delete")
    print("   - Intercepts delete -> update with deletedAt")
    print("   - Filters out soft-deleted records from reads")
    print()
    print("4. Slow Query Logging (innermost)")
    print("   - Logs queries taking longer than 100ms")

middleware_stack()

What's Next

Next: Prisma Errors for error handling.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro