snake-query
Version:
Node.js SDK for Snake Query API
509 lines (434 loc) • 12.8 kB
Markdown
# Schema Building Guide
Learn how to create powerful response schemas using SnakeQuery's SchemaBuilder for consistent, type-safe results.
## 🎯 Why Use Schemas?
Schemas ensure your query responses are:
- **Consistent**: Same structure every time
- **Type-safe**: Guaranteed data types
- **Validated**: Required fields are present
- **Documented**: Clear data contracts
## 🏗️ SchemaBuilder Basics
### Import SchemaBuilder
```javascript
const SnakeQuery = require('snake-query');
const { SchemaBuilder } = SnakeQuery;
```
### Basic Pattern
```javascript
const schema = SchemaBuilder.create()
.type() // Define base type
.constraints() // Add constraints
.build(); // Generate final schema
```
## 📊 Basic Types
### String
```javascript
const nameSchema = SchemaBuilder.create()
.string({
minLength: 1,
maxLength: 100
})
.build();
// Usage in query
const result = await client.query({
query: 'Extract user names',
data: users,
responseSchema: SchemaBuilder.create()
.array(nameSchema)
.build()
});
```
### Number
```javascript
const priceSchema = SchemaBuilder.create()
.number({
minimum: 0,
maximum: 10000
})
.build();
// Integer (treated as number internally)
const quantitySchema = SchemaBuilder.create()
.integer({
minimum: 1
})
.build();
```
### Boolean
```javascript
const availableSchema = SchemaBuilder.create()
.boolean()
.build();
```
## 🏢 Object Schemas
### Basic Object
```javascript
const userSchema = SchemaBuilder.create()
.object()
.addStringProperty('name')
.addNumberProperty('age', { minimum: 0 })
.addStringProperty('email')
.required(['name', 'age'])
.build();
```
### Complex Object
```javascript
const productSchema = SchemaBuilder.create()
.object()
.addStringProperty('title')
.addNumberProperty('price', { minimum: 0 })
.addStringProperty('description', { maxLength: 500 })
.addBooleanProperty('inStock')
.addArrayProperty('tags', { type: 'string' })
.required(['title', 'price'])
.description('Product information')
.build();
```
### Nested Objects
```javascript
const orderSchema = SchemaBuilder.create()
.object()
.addStringProperty('orderId')
.addObjectProperty('customer', {
name: { type: 'string' },
email: { type: 'string' },
phone: { type: 'string' }
})
.addObjectProperty('shipping', {
address: { type: 'string' },
city: { type: 'string' },
zipCode: { type: 'string' }
})
.required(['orderId', 'customer'])
.build();
```
## 📋 Array Schemas
### Simple Arrays
```javascript
// Array of strings
const tagsSchema = SchemaBuilder.create()
.array({ type: 'string' })
.build();
// Array of numbers
const ratingsSchema = SchemaBuilder.create()
.array({ type: 'number', minimum: 1, maximum: 5 })
.build();
```
### Array of Objects
```javascript
const productsSchema = SchemaBuilder.create()
.array(
SchemaBuilder.create()
.object()
.addStringProperty('name')
.addNumberProperty('price', { minimum: 0 })
.addStringProperty('category')
.required(['name', 'price'])
.build()
)
.build();
// Usage
const result = await client.query({
query: 'Show all products with name, price and category',
fetchUrl: 'https://api.store.com/products',
responseSchema: productsSchema
});
```
### Array Constraints
```javascript
const limitedProductsSchema = SchemaBuilder.create()
.array(productSchema)
.minItems(1) // At least 1 item
.maxItems(10) // At most 10 items
.build();
```
## 🎯 Advanced Patterns
### Conditional Fields
```javascript
const userProfileSchema = SchemaBuilder.create()
.object()
.addStringProperty('name')
.addStringProperty('email')
.addStringProperty('role') // Optional field
.addStringProperty('department') // Optional field
.required(['name', 'email']) // Only name and email required
.build();
```
### Multiple Types in Arrays
```javascript
const mixedDataSchema = SchemaBuilder.create()
.array(
SchemaBuilder.create()
.object()
.addStringProperty('type') // 'user' or 'order'
.addStringProperty('id')
.addObjectProperty('data', {}) // Flexible data field
.required(['type', 'id'])
.build()
)
.build();
```
### Reusable Schema Components
```javascript
// Define reusable schemas
const addressSchema = SchemaBuilder.create()
.object()
.addStringProperty('street')
.addStringProperty('city')
.addStringProperty('zipCode')
.addStringProperty('country')
.required(['street', 'city'])
.build();
const personSchema = SchemaBuilder.create()
.object()
.addStringProperty('name')
.addStringProperty('email')
.addProperty('address', addressSchema)
.required(['name', 'email'])
.build();
// Use in larger schemas
const employeeSchema = SchemaBuilder.create()
.object()
.addProperty('personalInfo', personSchema)
.addStringProperty('employeeId')
.addStringProperty('department')
.required(['personalInfo', 'employeeId'])
.build();
```
## 📈 Real-World Examples
### E-commerce Product Catalog
```javascript
const catalogSchema = SchemaBuilder.create()
.object()
.addArrayProperty('products',
SchemaBuilder.create()
.object()
.addStringProperty('id')
.addStringProperty('name')
.addStringProperty('description')
.addNumberProperty('price', { minimum: 0 })
.addNumberProperty('originalPrice', { minimum: 0 })
.addStringProperty('category')
.addStringProperty('brand')
.addBooleanProperty('inStock')
.addNumberProperty('rating', { minimum: 0, maximum: 5 })
.addNumberProperty('reviewCount', { minimum: 0 })
.addArrayProperty('images', { type: 'string' })
.addArrayProperty('tags', { type: 'string' })
.required(['id', 'name', 'price', 'category'])
.build()
)
.addObjectProperty('pagination', {
total: { type: 'number' },
page: { type: 'number' },
limit: { type: 'number' }
})
.required(['products'])
.build();
const result = await client.query({
query: 'Show first 20 products with all details, include pagination info',
fetchUrl: 'https://api.store.com/products',
responseSchema: catalogSchema
});
```
### Sales Analytics Dashboard
```javascript
const salesAnalyticsSchema = SchemaBuilder.create()
.object()
.addObjectProperty('summary', {
totalRevenue: { type: 'number', minimum: 0 },
totalOrders: { type: 'number', minimum: 0 },
averageOrderValue: { type: 'number', minimum: 0 },
conversionRate: { type: 'number', minimum: 0, maximum: 100 }
})
.addArrayProperty('dailyStats',
SchemaBuilder.create()
.object()
.addStringProperty('date')
.addNumberProperty('revenue', { minimum: 0 })
.addNumberProperty('orders', { minimum: 0 })
.addNumberProperty('visitors', { minimum: 0 })
.required(['date', 'revenue', 'orders'])
.build()
)
.addArrayProperty('topProducts',
SchemaBuilder.create()
.object()
.addStringProperty('productName')
.addNumberProperty('sales', { minimum: 0 })
.addNumberProperty('revenue', { minimum: 0 })
.required(['productName', 'sales', 'revenue'])
.build()
)
.addArrayProperty('insights', { type: 'string' })
.required(['summary', 'dailyStats'])
.build();
const result = await client.query({
query: 'Create comprehensive sales dashboard with daily stats, top products, and key insights',
fetchUrl: 'https://api.company.com/sales',
responseSchema: salesAnalyticsSchema
});
```
### User Management System
```javascript
const userManagementSchema = SchemaBuilder.create()
.array(
SchemaBuilder.create()
.object()
.addStringProperty('userId')
.addStringProperty('username')
.addStringProperty('email')
.addStringProperty('firstName')
.addStringProperty('lastName')
.addStringProperty('role')
.addStringProperty('department')
.addBooleanProperty('isActive')
.addStringProperty('lastLoginDate')
.addStringProperty('createdAt')
.addObjectProperty('permissions', {
canRead: { type: 'boolean' },
canWrite: { type: 'boolean' },
canDelete: { type: 'boolean' },
canAdmin: { type: 'boolean' }
})
.required(['userId', 'username', 'email', 'role', 'isActive'])
.build()
)
.build();
const result = await client.query({
query: 'List all active users with their roles, departments, and permissions',
fetchUrl: 'https://api.company.com/users',
responseSchema: userManagementSchema
});
```
## ⚡ Schema Performance Tips
### 1. Keep Schemas Focused
```javascript
// ✅ Good: Specific schema for specific use case
const productListSchema = SchemaBuilder.create()
.array(
SchemaBuilder.create()
.object()
.addStringProperty('name')
.addNumberProperty('price')
.required(['name', 'price'])
.build()
)
.build();
// ❌ Avoid: Overly complex schemas with unused fields
const overComplexSchema = SchemaBuilder.create()
.array(
SchemaBuilder.create()
.object()
.addStringProperty('name')
.addNumberProperty('price')
.addStringProperty('description')
.addArrayProperty('reviews', { type: 'object' })
.addObjectProperty('metadata', {})
// ... many more fields that won't be used
.build()
)
.build();
```
### 2. Use Appropriate Constraints
```javascript
// ✅ Good: Reasonable constraints
const userSchema = SchemaBuilder.create()
.object()
.addStringProperty('name', { minLength: 1, maxLength: 100 })
.addNumberProperty('age', { minimum: 0, maximum: 150 })
.required(['name'])
.build();
// ❌ Avoid: Overly restrictive constraints
const restrictiveSchema = SchemaBuilder.create()
.object()
.addStringProperty('name', { minLength: 5, maxLength: 10 }) // Too restrictive
.addNumberProperty('age', { minimum: 18, maximum: 65 }) // Excludes valid cases
.build();
```
### 3. Reuse Schema Components
```javascript
// ✅ Good: Define once, use multiple times
const addressSchema = SchemaBuilder.create()
.object()
.addStringProperty('street')
.addStringProperty('city')
.build();
const userSchema = SchemaBuilder.create()
.object()
.addProperty('homeAddress', addressSchema)
.addProperty('workAddress', addressSchema)
.build();
```
## 🚨 Common Mistakes
### 1. Missing Required Fields
```javascript
// ❌ Bad: Forgetting to mark essential fields as required
const productSchema = SchemaBuilder.create()
.object()
.addStringProperty('name')
.addNumberProperty('price')
// Missing: .required(['name', 'price'])
.build();
// ✅ Good: Always specify required fields
const productSchema = SchemaBuilder.create()
.object()
.addStringProperty('name')
.addNumberProperty('price')
.required(['name', 'price'])
.build();
```
### 2. Inconsistent Property Names
```javascript
// ❌ Bad: Inconsistent naming
const userSchema = SchemaBuilder.create()
.object()
.addStringProperty('user_name') // Snake case
.addStringProperty('firstName') // Camel case
.addStringProperty('last-name') // Kebab case
.build();
// ✅ Good: Consistent naming convention
const userSchema = SchemaBuilder.create()
.object()
.addStringProperty('userName')
.addStringProperty('firstName')
.addStringProperty('lastName')
.build();
```
### 3. Overly Complex Nested Structures
```javascript
// ❌ Avoid: Too many levels of nesting
const complexSchema = SchemaBuilder.create()
.object()
.addObjectProperty('level1', {
level2: {
type: 'object',
properties: {
level3: {
type: 'object',
properties: {
level4: { type: 'string' }
}
}
}
}
})
.build();
// ✅ Better: Flatten structure or use separate schemas
const nestedSchema = SchemaBuilder.create()
.object()
.addStringProperty('id')
.addObjectProperty('details', {
name: { type: 'string' },
value: { type: 'string' }
})
.build();
```
## 🎯 Best Practices Summary
1. **Start Simple**: Begin with basic schemas and add complexity as needed
2. **Be Specific**: Define exact field names and types you expect
3. **Use Constraints**: Set appropriate minimum/maximum values
4. **Mark Required Fields**: Always specify which fields are essential
5. **Keep It Focused**: Create schemas for specific use cases
6. **Reuse Components**: Define common patterns once
7. **Test Your Schemas**: Verify they work with real data
8. **Document Complex Schemas**: Add descriptions for clarity
With these patterns and practices, you can create robust, maintainable schemas that ensure consistent, type-safe responses from your SnakeQuery operations.