How To Handle Prisma Unique Constraints with a Friendly Error

Often, your data model will have a unique constraint to prevent duplicate records.

For example:

model Character {
  id        Int       @id @default(autoincrement())
  name      String    @unique
  appearsIn Episode[]
}

A Character has to have a unique name. No two characters can be named the same.

If you try to add a new Character with the same name – or update an existing Character with a name that already exists – then your database and Prisma will prevent the data change from happen and raise an error.

Unless you handle this error, the RedwoodJS GraphQL API will mask the error with the Something went wrong message and a stack trace will be shown in the logs.

Alternatively, you could check the existence of the record or use the [validateUniqueness Service Validation[(Services | RedwoodJS Docs), but that requires two database transactions per request.

What if you could simple more gracefully handle the error?

Prisma defines many error codes and one of them P2002 is for an error when the Unique constraint failed.

Therefore, we can try/catch the error, check to see if it due to a unique constraint, and throw a RedwoodError that will send a more friendly message back through the GraphQL response.

The following code for the characters service demonstrates how to catch and handle the P2002 unique constraint failed error.

import type { QueryResolvers, MutationResolvers } from 'types/graphql'
import { Prisma } from '@prisma/client'
import { RedwoodError } from '@redwoodjs/api'

import { db } from 'src/lib/db'
import { logger } from 'src/lib/logger'

export const createCharacter: MutationResolvers['createCharacter'] = async ({
  input,
}) => {
  try {
    return await db.character.create({
      data: input,
    })
  } catch (e) {
    logger.error(e, 'Error creating character')

    if (e instanceof Prisma.PrismaClientKnownRequestError) {
      // P2022: Unique constraint failed
      // Prisma error codes: https://www.prisma.io/docs/reference/api-reference/error-reference#error-codes
      if (e.code === 'P2002') {
        logger.error('The character already exists', e)
        throw new RedwoodError('The character already exists')
      }
    }
    throw e
  }
}

export const updateCharacter: MutationResolvers['updateCharacter'] = async ({
  id,
  input,
}) => {
  try {
    return await db.character.update({
      data: input,
      where: { id },
    })
  } catch (e) {
    logger.error(e, 'Error updating character')

    if (e instanceof Prisma.PrismaClientKnownRequestError) {
      // P2022: Unique constraint failed
      // Prisma error codes: https://www.prisma.io/docs/reference/api-reference/error-reference#error-codes
      if (e.code === 'P2002') {
        logger.error('The character already exists', e)
        throw new RedwoodError('The character already exists')
      }
    }
    throw e
  }
}

Note: It is important here that your service is async/await. Redwood services generate without this, but is needed to return the error properly.

Now you have given the user a better experience and can inform them why the create or update failed.

To explore more Prisma error code, see: error codes in the Prisma reference documentation.

Next

Maybe one can parse the error info

api | 11:47:35 🐛 graphql-server GraphQL execution started: CreateCharacterMutation
api | 11:47:35 🚨 Error creating character 
api | 
api | 🚨 PrismaClientKnownRequestError Info
api | 
api | {
api |   "code": "P2002",
api |   "clientVersion": "4.3.1",
api |   "meta": {
api |     "target": [
api |       "name"
api |     ]
api |   }
api | }

To enrich the message with the field names and/or values that violated the constraint?

2 Likes