Skip to main content

Error Handling

Prisma Client throws specific error types that help you handle different failure scenarios.

Error Types

Prisma Client provides five error classes:

PrismaClientKnownRequestError

Database errors with known error codes:
import { PrismaClientKnownRequestError } from '@prisma/client/runtime/library'

try {
  await prisma.user.create({
    data: {
      email: 'duplicate@example.com'
    }
  })
} catch (error) {
  if (error instanceof PrismaClientKnownRequestError) {
    console.log('Error code:', error.code)
    console.log('Meta:', error.meta)
    console.log('Message:', error.message)
  }
}
Properties:
code
string
Error code (e.g., P2002 for unique constraint violation)
meta
object
Additional error metadata (constraint name, field names, etc.)
clientVersion
string
Prisma Client version
batchRequestIdx
number
Index of failed operation in batch transaction (if applicable)
Source: /home/daytona/workspace/source/packages/client-runtime-utils/src/errors/PrismaClientKnownRequestError.ts:11-33

PrismaClientUnknownRequestError

Database errors without a known error code:
import { PrismaClientUnknownRequestError } from '@prisma/client/runtime/library'

try {
  await prisma.$queryRaw`INVALID SQL`
} catch (error) {
  if (error instanceof PrismaClientUnknownRequestError) {
    console.log('Unknown error:', error.message)
  }
}

PrismaClientValidationError

Invalid Prisma Client API usage:
import { PrismaClientValidationError } from '@prisma/client/runtime/library'

try {
  await prisma.user.create({
    data: {
      // Missing required field
    }
  })
} catch (error) {
  if (error instanceof PrismaClientValidationError) {
    console.log('Validation error:', error.message)
  }
}
Common causes:
  • Missing required fields
  • Invalid field types
  • Invalid query structure
  • Wrong method arguments

PrismaClientRustPanicError

Unexpected panic in the query compiler:
import { PrismaClientRustPanicError } from '@prisma/client/runtime/library'

try {
  await prisma.user.findMany()
} catch (error) {
  if (error instanceof PrismaClientRustPanicError) {
    console.error('Rust panic:', error.message)
    // This is usually a bug - please report it
  }
}

PrismaClientInitializationError

Errors during client initialization:
import { PrismaClientInitializationError } from '@prisma/client/runtime/library'

try {
  const prisma = new PrismaClient({
    adapter: invalidAdapter
  })
  await prisma.$connect()
} catch (error) {
  if (error instanceof PrismaClientInitializationError) {
    console.error('Initialization error:', error.message)
  }
}
Common causes:
  • Invalid adapter configuration
  • Database connection failures
  • Missing adapter or accelerateUrl
  • Invalid constructor options

Error Codes

Known error codes in PrismaClientKnownRequestError.code:

Common Error Codes

CodeDescription
P2000Value too long for column
P2001Record not found
P2002Unique constraint violation
P2003Foreign key constraint violation
P2004Constraint violation
P2005Invalid value for field type
P2006Invalid value
P2007Data validation error
P2008Failed to parse query
P2009Failed to validate query
P2010Raw query failed
P2011Null constraint violation
P2012Missing required value
P2013Missing required argument
P2014Relation violation
P2015Related record not found
P2016Query interpretation error
P2017Records not connected
P2018Required connected records not found
P2019Input error
P2020Value out of range
P2021Table does not exist
P2022Column does not exist
P2023Inconsistent column data
P2024Connection timeout
P2025Record to update/delete not found
P2026Unsupported database feature
P2027Multiple errors occurred
P2028Transaction API error
P2029Query parameter limit exceeded
P2030Full-text search index not found
P2033Number out of range
P2034Transaction failed due to write conflict

Handling Specific Errors

Unique Constraint Violation (P2002)

try {
  await prisma.user.create({
    data: {
      email: 'alice@example.com'
    }
  })
} catch (error) {
  if (
    error instanceof PrismaClientKnownRequestError &&
    error.code === 'P2002'
  ) {
    console.log('Email already exists')
    console.log('Field:', error.meta?.target)  // ['email']
  }
}

Record Not Found (P2025)

try {
  await prisma.user.update({
    where: { id: 'nonexistent' },
    data: { name: 'New Name' }
  })
} catch (error) {
  if (
    error instanceof PrismaClientKnownRequestError &&
    error.code === 'P2025'
  ) {
    console.log('User not found')
  }
}

// Or use findUniqueOrThrow for explicit handling
try {
  const user = await prisma.user.findUniqueOrThrow({
    where: { id: 'nonexistent' }
  })
} catch (error) {
  if (error.code === 'P2025') {
    console.log('User not found')
  }
}

Foreign Key Constraint (P2003)

try {
  await prisma.post.create({
    data: {
      title: 'My Post',
      authorId: 'nonexistent-user'
    }
  })
} catch (error) {
  if (
    error instanceof PrismaClientKnownRequestError &&
    error.code === 'P2003'
  ) {
    console.log('Referenced user does not exist')
    console.log('Field:', error.meta?.field_name)
  }
}

Transaction Timeout (P2028)

try {
  await prisma.$transaction(
    async (tx) => {
      await tx.user.create({ data: { email: 'alice@example.com' } })
      await new Promise(resolve => setTimeout(resolve, 6000))
    },
    { timeout: 5000 }
  )
} catch (error) {
  if (
    error instanceof PrismaClientKnownRequestError &&
    error.code === 'P2028'
  ) {
    console.log('Transaction timeout')
  }
}

Write Conflict (P2034)

try {
  await prisma.$transaction(
    async (tx) => {
      // Concurrent update conflict
    },
    { isolationLevel: 'Serializable' }
  )
} catch (error) {
  if (
    error instanceof PrismaClientKnownRequestError &&
    error.code === 'P2034'
  ) {
    console.log('Transaction write conflict - retry operation')
  }
}

Error Handling Patterns

Type Guard Helper

function isPrismaError(
  error: unknown,
  code: string
): error is PrismaClientKnownRequestError {
  return (
    error instanceof PrismaClientKnownRequestError &&
    error.code === code
  )
}

// Usage
try {
  await prisma.user.create({ data: { email: 'alice@example.com' } })
} catch (error) {
  if (isPrismaError(error, 'P2002')) {
    console.log('Unique constraint violation')
  }
}

Centralized Error Handler

function handlePrismaError(error: unknown) {
  if (error instanceof PrismaClientKnownRequestError) {
    switch (error.code) {
      case 'P2002':
        return { status: 409, message: 'Resource already exists' }
      case 'P2025':
        return { status: 404, message: 'Resource not found' }
      case 'P2003':
        return { status: 400, message: 'Invalid reference' }
      case 'P2028':
        return { status: 408, message: 'Request timeout' }
      default:
        return { status: 500, message: 'Database error' }
    }
  }
  
  if (error instanceof PrismaClientValidationError) {
    return { status: 400, message: 'Invalid request' }
  }
  
  return { status: 500, message: 'Internal server error' }
}

// Usage in API route
app.post('/users', async (req, res) => {
  try {
    const user = await prisma.user.create({ data: req.body })
    res.json(user)
  } catch (error) {
    const { status, message } = handlePrismaError(error)
    res.status(status).json({ error: message })
  }
})

Retry Logic

async function withRetry<T>(
  operation: () => Promise<T>,
  maxRetries = 3
): Promise<T> {
  for (let i = 0; i < maxRetries; i++) {
    try {
      return await operation()
    } catch (error) {
      // Retry on write conflicts
      if (
        error instanceof PrismaClientKnownRequestError &&
        error.code === 'P2034' &&
        i < maxRetries - 1
      ) {
        await new Promise(resolve => setTimeout(resolve, 100 * (i + 1)))
        continue
      }
      throw error
    }
  }
  
  throw new Error('Max retries exceeded')
}

// Usage
const result = await withRetry(() =>
  prisma.$transaction(async (tx) => {
    // Transaction operations
  }, { isolationLevel: 'Serializable' })
)

Graceful Degradation

async function getUser(id: string) {
  try {
    return await prisma.user.findUniqueOrThrow({
      where: { id }
    })
  } catch (error) {
    if (
      error instanceof PrismaClientKnownRequestError &&
      error.code === 'P2025'
    ) {
      // Return default user instead of failing
      return {
        id,
        email: 'unknown@example.com',
        name: 'Unknown User'
      }
    }
    throw error
  }
}

Batch Transaction Errors

Batch transactions report which operation failed:
try {
  await prisma.$transaction([
    prisma.user.create({ data: { email: 'alice@example.com' } }),  // Index 0
    prisma.user.create({ data: { email: 'alice@example.com' } }),  // Index 1 - fails
    prisma.post.create({ data: { title: 'Hello' } })                // Index 2
  ])
} catch (error) {
  if (error instanceof PrismaClientKnownRequestError) {
    console.log('Failed at index:', error.batchRequestIdx)  // 1
    console.log('Error code:', error.code)                   // P2002
  }
}

Driver Adapter Errors

Driver adapters map database-specific errors to Prisma error codes:
// PostgreSQL unique violation -> P2002
// MySQL duplicate entry -> P2002
// SQLite UNIQUE constraint failed -> P2002

try {
  await prisma.user.create({
    data: { email: 'duplicate@example.com' }
  })
} catch (error) {
  // Same error code across all databases
  if (error.code === 'P2002') {
    console.log('Unique constraint violation')
  }
}
Error mapping is handled by each driver adapter’s error conversion logic.

Raw Query Errors

Raw queries always return error code P2010:
try {
  await prisma.$queryRaw`INVALID SQL SYNTAX`
} catch (error) {
  if (
    error instanceof PrismaClientKnownRequestError &&
    error.code === 'P2010'
  ) {
    console.log('Raw query failed:', error.message)
  }
}

Connection Errors

try {
  await prisma.$connect()
} catch (error) {
  if (error instanceof PrismaClientInitializationError) {
    console.error('Failed to connect:', error.message)
    
    // Common causes:
    // - Invalid connection string
    // - Database server not running
    // - Network issues
    // - Invalid credentials
  }
}

Best Practices

Always Handle Specific Errors

// ❌ Bad: Generic catch
try {
  await prisma.user.create({ data: { email: 'alice@example.com' } })
} catch (error) {
  console.error('Error:', error)
}

// ✓ Good: Specific handling
try {
  await prisma.user.create({ data: { email: 'alice@example.com' } })
} catch (error) {
  if (error instanceof PrismaClientKnownRequestError) {
    if (error.code === 'P2002') {
      // Handle duplicate
    } else if (error.code === 'P2003') {
      // Handle foreign key
    }
  }
  throw error  // Re-throw unknown errors
}

Log Errors with Context

try {
  await prisma.user.update({
    where: { id: userId },
    data: { name: newName }
  })
} catch (error) {
  console.error('Failed to update user:', {
    userId,
    newName,
    error: error instanceof Error ? error.message : error,
    code: error instanceof PrismaClientKnownRequestError ? error.code : undefined
  })
  throw error
}

Use Type Guards

function isPrismaClientError(error: unknown): error is PrismaClientKnownRequestError {
  return error instanceof PrismaClientKnownRequestError
}

try {
  await prisma.user.create({ data: { email: 'alice@example.com' } })
} catch (error) {
  if (isPrismaClientError(error)) {
    console.log('Prisma error:', error.code)
  } else {
    console.log('Unknown error:', error)
  }
}

Test Error Scenarios

// In your tests
test('handles duplicate email', async () => {
  await prisma.user.create({ data: { email: 'test@example.com' } })
  
  await expect(
    prisma.user.create({ data: { email: 'test@example.com' } })
  ).rejects.toMatchObject({
    code: 'P2002'
  })
})

Next Steps

Transactions

Handle transaction-specific errors

Middleware

Intercept and handle errors globally