Skip to content

Instantly share code, notes, and snippets.

@thim81
Last active January 5, 2024 07:43
Show Gist options
  • Save thim81/70383609b724ebab54b256696d07d488 to your computer and use it in GitHub Desktop.
Save thim81/70383609b724ebab54b256696d07d488 to your computer and use it in GitHub Desktop.
Prisma Simple Schema Validation
import path, {dirname} from "path";
import {fileURLToPath} from "url";
import prismaInternals from '@prisma/internals';
const {getDMMF} = prismaInternals;
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
export const validateRequestPayload = async (modelName, requestBody) => {
try {
// Get Prisma DMMF
const dmmf = await getDMMF({
datamodelPath: path.join(__dirname, '../../prisma/schema.prisma'),
});
// Find the model schema by name
const modelSchema = dmmf.datamodel.models.find((model) => model.name === modelName);
if (!modelSchema) {
throw new Error(`Model ${modelName} not found in the schema.`);
}
// Array to store validation errors
const validationErrors = [];
// Filter out fields not present in the model schema
const validFields = Object.keys(requestBody).filter((fieldName) =>
modelSchema.fields.some((field) => field.name === fieldName)
);
// Check for unexpected fields in the request body
const unexpectedFields = Object.keys(requestBody).filter((fieldName) => !validFields.includes(fieldName));
// If there are unexpected fields, include as error
unexpectedFields.forEach(fieldName => {
validationErrors.push({
field: fieldName,
message: `Field '${fieldName}' is not expected in the request body.`
});
});
// Validate request payload with Prisma Schema fields
for (const field of modelSchema.fields) {
const fieldName = field.name;
const fieldType = field.type;
// Check if the field is required but missing in the request body
if (field.isRequired && !field.hasDefaultValue && !(fieldName in requestBody)) {
validationErrors.push({ field: fieldName, message: `Field '${fieldName}' is required.` });
}
// Check if the field type matches the expected type
if (fieldName in requestBody && typeof requestBody[fieldName] !== fieldType.toLowerCase()) {
validationErrors.push({ field: fieldName, message: `Field '${fieldName}' must be of type '${fieldType}'.` });
}
// Check if the field is a list but not provided as an array
if (field.isList && !(fieldName in requestBody) && !Array.isArray(requestBody[fieldName])) {
validationErrors.push({ field: fieldName, message: `Field '${fieldName}' must be an array.` });
}
// Check if the field is generated and provided in the request body
if (field.isGenerated && fieldName in requestBody) {
validationErrors.push({ field: fieldName, message: `Field '${fieldName}' is generated and cannot be provided in the request.` });
}
// Check if the field is automatically updated and provided in the request body
if (field.isUpdatedAt && fieldName in requestBody) {
validationErrors.push({ field: fieldName, message: `Field '${fieldName}' is automatically updated and cannot be provided in the request.` });
}
}
// If there are validation errors, throw an error with the details
if (validationErrors.length > 0) {
const validationError = new Error('Validation Failed');
validationError.code = 'VALIDATION_ERROR';
validationError.details = validationErrors;
throw validationError;
}
return requestBody;
} catch (error) {
throw error;
}
}
@thim81
Copy link
Author

thim81 commented Jan 4, 2024

I'm using this code to plug into an ExpressJS application to validate the request body, based on the Prisma Schema.

The example below, will check the req.body against the "Character" model from Prisma.

post("/characters", async (req, res) => {
  try {
    // Validate
    const validatedPayload = await validateRequestPayload('Character', req.body);

    const newResource = await prisma.character.create({
      data: req.body,
    });

    res.json(newResource);
  } catch (error) {
    if (error.code === 'VALIDATION_ERROR') {
      // Handle validation error, e.g., send a 400 Bad Request response
      res.status(422).json({ error: error.message });
    }

    if (error.code === 'P2002' && error.meta?.target.includes('name')) {
      // Prisma error code 'P2002' corresponds to unique constraint violation
      // Check if the error is related to the 'name' field
      return res.status(422).json({error: 'Name must be unique.'});
    }

    console.error("Error creating character:", error);
    res.status(500).json({error: "Internal Server Error"});
  }
});

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment