🎯 Overriding Existing Schemas
This guide demonstrates how to override and extend existing Nodeblocks schemas to customize validation rules, add new fields, or modify existing field constraints. We'll show how to extend the built-in profileSchema with custom validation rules and additional required fields. Because the SDK validates through AJV against JSON Schema draft-07, you can use the full draft-07 keyword set directly in withSchema.
🏗️ Schema Override Architecture
Schema overriding allows you to:
- Extend existing schemas - Build upon pre-defined schemas from the SDK
- Add custom validation - Implement domain-specific validation rules
- Modify field constraints - Change min/max values, patterns, or types
- Control required fields - Make fields required or optional as needed
1️⃣ Understanding Built-in Schemas
Nodeblocks provides pre-built schemas for common entities. The profileSchema includes standard profile fields:
// Built-in profileSchema structure
{
$schema: 'http://json-schema.org/draft-07/schema#',
type: 'object',
additionalProperties: false,
properties: {
avatar: {
oneOf: [avatarSchema, { type: 'null' }],
},
identityId: { type: 'string' },
name: { type: 'string' },
},
}
createProfileSchema builds on this and requires identityId and name.
Draft-07 Support
The SDK compiles your schemas with AJV in draft-07 mode, so draft-07 keywords are available without any extra adapter layer.
Common draft-07 keywords you can use include:
allOf,anyOf,oneOf,notif,then,elseconst,enumpattern,patternPropertiesdependencies,containsdefinitions,format,default
The SDK also applies its own object defaults while walking supported schema branches:
additionalProperties: falseis applied to object schemas unless you explicitly override itqueryFilter: trueis applied to object schemas to protect MongoDB-style query filters
That defaulting pass walks properties, allOf, oneOf, anyOf, and array items. For legacy withSchema(schemaA, schemaB) usage, the SDK deep-merges same-type schemas before compiling them. For OpenAPI-style withSchema({ requestBody, parameters }), the SDK enhances the supplied schemas with these defaults before compiling them.
import {primitives} from '@nodeblocks/backend-sdk';
const {withSchema} = primitives;
export const accountSchema = withSchema({
requestBody: {
content: {
'application/json': {
schema: {
additionalProperties: false,
if: {
properties: {
accountType: {const: 'organization'},
},
required: ['accountType'],
},
then: {
required: ['organizationName'],
},
else: {
not: {
required: ['organizationName'],
},
},
properties: {
accountType: {enum: ['personal', 'organization']},
organizationName: {type: 'string', minLength: 2},
},
required: ['accountType'],
type: 'object',
},
},
},
required: true,
},
});
2️⃣ Basic Schema Override
Here's how to override an existing schema with custom validation:
import {primitives, schemas} from '@nodeblocks/backend-sdk';
const {withSchema} = primitives;
const {profileSchema} = schemas;
export const createCustomProfileSchema = withSchema({
requestBody: {
content: {
'application/json': {
schema: {
// Spread the existing schema
...profileSchema,
properties: {
// Spread existing properties
...profileSchema.properties,
// Override or add new properties
age: {
type: 'number',
minimum: 18,
maximum: 100,
},
},
// Preserve existing required fields and add age
required: [...(profileSchema.required || ['identityId', 'name']), 'age'],
},
},
},
required: true,
},
});
You can then compose the custom schema with an existing route:
import {primitives, routes} from '@nodeblocks/backend-sdk';
import {createCustomProfileSchema} from './schemas/customProfileSchema';
const {compose} = primitives;
const {createProfileRoute} = routes;
export const createCustomProfileFeature = compose(createCustomProfileSchema, createProfileRoute);
// Use createCustomProfileFeature instead of features.createProfileFeature
3️⃣ Schema Override Patterns
Pattern 1: Adding Fields
// Add new optional fields
schema: {
...existingSchema,
properties: {
...existingSchema.properties,
newField: { type: 'string' }
}
}
Pattern 2: Making Fields Required
// Make existing optional fields required (always merge, never replace)
schema: {
...existingSchema,
required: [...(existingSchema.required || []), 'existingField']
}
Pattern 3: Overriding Field Constraints
// Override existing field with stricter validation
properties: {
...existingSchema.properties,
name: {
...existingSchema.properties.name,
minLength: 2,
maxLength: 80
}
}
Pattern 4: Removing Fields
// Remove fields by destructuring, then drop them from required too
const { unwantedField, ...allowedProperties } = existingSchema.properties;
const required = (existingSchema.required || []).filter(
(field) => field !== 'unwantedField'
);
schema: {
...existingSchema,
properties: allowedProperties,
required,
}
➡️ Next Steps
Now you can enhance your schemas by:
- Adding custom validation rules - Implement business-specific constraints
- Creating reusable schema components - Build a library of common schema patterns
- Implementing complex validation - Use conditional schemas and custom validators
- Testing schema validation - Create comprehensive test suites for your overrides
🔗 See Also
- Creating a Composite Service - Combine multiple services with custom schemas
- Profile Service - Profile schema and endpoint reference
- Schema Component - Understand schema composition
- Service Component - Understand service architecture
- Feature Component - Learn about feature composition
- Error Handling - Handle validation errors properly