Models
Models represent your database tables and define the structure of your data. This guide explains how to create and use models in StratusTS.
What are Models?
Section titled “What are Models?”Models are TypeScript function that:
- Define the structure of database tables
- Specify column types and constraints
- Provide methods to query and manipulate data
- Ensure type safety when working with databases
Think of models as blueprints for your database tables.
Prerequisites
Section titled “Prerequisites”Before working with models:
- Configure database in
src/settings.ts(see Database Setup) - Ensure database connection is working
- Understand basic SQL/database concepts
Creating Your First Model
Section titled “Creating Your First Model”Basic User Model
Section titled “Basic User Model”Create a model in src/users/models.ts:
import { Model } from 'stratus-ts';
const User = Model('user', {name: { type: 'string', required: true, min: 2, max: 100,},email: { type: 'email', unique: true, required: true,},password: { type: 'string', required: true, select: false, // Don't return in queries by default},age: { type: 'number', min: 18, max: 100,},isActive: { type: 'bool', default: true,},});
export { User };Model Anatomy
Section titled “Model Anatomy”Model(tableName, schema) - Creates a model
- First argument: Database table name
- Second argument: Schema object defining fields
Field definition:
[fieldName]: { type: 'string', // Field type required: true, // Is field required? unique: true, // Must be unique? default: 'value', // Default value min: 2, // Minimum value/length max: 100, // Maximum value/length select: false, // Include in queries? # in development}Field Types
Section titled “Field Types”String Type
Section titled “String Type”const User = Model('user', { name: { type: 'string', min: 2, // Minimum length max: 100, // Maximum length required: true, },
username: { type: 'string', min: 3, max: 30, unique: true, // Must be unique required: true, },
bio: { type: 'string', max: 500, required: false, // Optional field },});Email Type
Section titled “Email Type”Special string type with email validation:
const User = Model('user', { email: { type: 'email', unique: true, required: true, },
recoveryEmail: { type: 'email', required: false, },});Number Type
Section titled “Number Type”const Product = Model('products', { price: { type: 'number', min: 0, // Minimum value max: 999999, // Maximum value required: true, },
stock: { type: 'number', default: 0, min: 0, },
rating: { type: 'number', min: 0, max: 5, default: 0, },});Boolean Type
Section titled “Boolean Type”const User = Model('user', { isActive: { type: 'bool', default: true, },
isVerified: { type: 'bool', default: false, },
isPremium: { type: 'bool', default: false, },});Enum Type
Section titled “Enum Type”Restricts values to specific options:
const User = Model('user', { gender: { type: 'enum', values: ['male', 'female', 'other'], default: 'other', },
role: { type: 'enum', values: ['user', 'admin', 'moderator'], default: 'user', required: true, },
status: { type: 'enum', values: ['active', 'inactive', 'banned', 'pending'], default: 'pending', },});Timestamp Type
Section titled “Timestamp Type”For date and time values:
const User = Model('user', { createdAt: { type: 'timestamp', default: 'now', // Set to current time },
updatedAt: { type: 'timestamp', default: 'now', },
lastLogin: { type: 'timestamp' },
verifiedAt: { type: 'timestamp' },});Field Options
Section titled “Field Options”required
Section titled “required”Makes field mandatory:
const User = Model('user', { email: { type: 'email', required: true, // Must be provided },
phone: { type: 'string', // Optional },});unique
Section titled “unique”Ensures field values are unique across all records:
const User = Model('user', { email: { type: 'email', unique: true, // No duplicate emails },
username: { type: 'string', unique: true, // No duplicate usernames }, age:{ type: 'number' // Optional }});default
Section titled “default”Sets default value if not provided:
const User = Model('user', { role: { type: 'enum', values: ['user', 'admin', 'moderator'], default: 'user', },
isActive: { type: 'bool', default: true, },
createdAt: { type: 'timestamp', default: 'now', },
points: { type: 'number', default: 0, },});select
Section titled “select”Controls if field is returned in queries:
const User = Model('user', { password: { type: 'string', required: true, select: false, // Don't include in query results },
email: { type: 'email', required: true, select: true, // Include in results (default) don't need to add },});min / max
Section titled “min / max”For strings (length) and numbers (value):
const User = Model('user', { username: { type: 'string', min: 3, // Minimum 3 characters max: 30, // Maximum 30 characters },
age: { type: 'number', min: 13, // Minimum value 13 max: 120, // Maximum value 120 },
bio: { type: 'string', max: 500, // Maximum 500 characters },});Complete Model Example
Section titled “Complete Model Example”import { Model } from 'stratus-ts';
const User = Model('user', {// Basic infousername: { type: 'string', min: 3, max: 30, unique: true, required: true,},
email: { type: 'email', unique: true, required: true,},
password: { type: 'string', required: true, min: 8, select: false, // Doesn't return password by default untill ask},
// Profile infoname: { type: 'string', min: 2, max: 100,},
age: { type: 'number', min: 13, max: 120,},
gender: { type: 'enum', values: ['male', 'female', 'other'], default: 'other',},
bio: { type: 'string', max: 500,},
// Account statusrole: { type: 'enum', values: ['user', 'admin', 'moderator'], default: 'user',},
isActive: { type: 'bool', default: true,},
isVerified: { type: 'bool', default: false,},
// TimestampsverifiedAt: { type: 'timestamp',},
createdAt: { type: 'timestamp', default: 'now',},
lastLogin: { type: 'timestamp',},});
export { User };Using Models in Controllers
Section titled “Using Models in Controllers”Import Your Model
Section titled “Import Your Model”import { User } from './models';Create (Insert)
Section titled “Create (Insert)”const createUser: ControllerType = async (question, reply) => { const { body:{ username, email, password, age } } = question.body();
const user = await User.create({ username, email, password, age, });
reply.status(created()).json({ success: true, data: user, });};Read (Find)
Section titled “Read (Find)”Find all:
const listUsers: ControllerType = async (question, reply) => { const users = await User.find();
reply.status(ok()).json({ success: true, data: users, });};Find one by Primary Key/Id:
const getUser: ControllerType = async (question, reply) => { const { id } = question.params();
const user = await User.findByPK(id);
if (!user) { return reply.status(notFound()).json({ success: false, error: 'User not found', }); }
reply.status(ok()).json({ success: true, data: user, });};Find with conditions:
// Find by emailconst user = await User.findOne({ email: 'alice@example.com' });
// Find active usersconst activeUsers = await User.find({ isActive: true });
// Find verified usersconst verifiedUsers = await User.find({ isVerified: true });
// Find by multiple conditions// this will led to 'and' queryconst admins = await User.find({ role: 'admin', isActive: true,});Update
Section titled “Update”const updateUser: ControllerType = async (question, reply) => { const { id } = question.params(); const { body:{ name, bio, age } } = question.body();
const user = await User.findByPK(id);
if (!user) { return reply.status(notFound()).json({ success: false, error: 'User not found', }); }
// Update fields const updatedUser = await User.updateById(id, { name, bio, age, });
reply.status(ok()).json({ success: true, data: updatedUser, });};Delete
Section titled “Delete”const deleteUser: ControllerType = async (question, reply) => { const { id } = question.params();
const result = await User.destroyByPK(id);
if (!result) { return reply.status(notFound()).json({ success: false, error: 'User not found', }); }
reply.status(ok()).json({ success: true, message: 'User deleted successfully', });};Advanced Queries
Section titled “Advanced Queries”Pagination
Section titled “Pagination”const listUsers: ControllerType = async (question, reply) => { const { page = '1', limit = '10' } = question.query;
const pageNumber = parseInt(page); const pageLimit = parseInt(limit);
const users = await User.find(undefined, undefined, { skip: (pageNumber - 1) * pageLimit, limit: pageLimit, });
const total = (await User.find(undefined, {_id:true})).length;
reply.status(ok()).json({ success: true, data: users, pagination: { page: pageNumber, limit: pageLimit, total, pages: Math.ceil(total / pageLimit), }, });};Sorting
Section titled “Sorting”// Sort by createdAt descendingconst users = await User.find(undefined,undefined,{ orderBy:{ createdAt: 'DESC' }});
// Sort by username ascendingconst users = await User.find(undefined,undefined,{ orderBy:{ username:'ASC' }});
// Multiple sort fieldsconst users = await User.find(undefined,undefined,{ orderBy:{ role:'ASC', createdAt: 'DESC' }});Selecting Specific Fields
Section titled “Selecting Specific Fields”// Only get username and emailconst users = await User.find(undefined,{ email: true, username: true});
// Exclude password (if select: false)const users = await User.find(undefined,{ email: true, username: true});
// Include password (if select: false)const users = await User.find(undefined,{ email: true, username: true, password: true});Counting Records
Section titled “Counting Records”Upcomming
Section titled “Upcomming”Search/Query
Section titled “Search/Query”// Search by usernameconst users = await User.find({ username:'john'});
// Search in multiple fieldsconst users = await User.find({ email: 'test@example.com', username:'john'});Validation
Section titled “Validation”Models automatically validate based on schema:
const User = Model('user', { email: { type: 'email', // Validates email format required: true, // Validates presence unique: true, // Validates uniqueness }, age: { type: 'number', min: 18, // Validates minimum value max: 120, // Validates maximum value }, username: { type: 'string', min: 3, // Validates minimum length max: 30, // Validates maximum length }, gender: { type: 'enum', values: ['male', 'female', 'other'], // Validates allowed values },});
// This will fail validationtry { await User.create({ email: 'invalid-email', // Invalid email format age: 15, // Below minimum username: 'ab', // Too short gender: 'unknown', // Not in enum values });} catch (error) { // Validation error with details console.error(error.message);}Best Practices
Section titled “Best Practices”1. Use Descriptive Field Names
Section titled “1. Use Descriptive Field Names”// ✅ Goodconst User = Model('user', { username: { type: 'string' }, isEmailVerified: { type: 'bool' }, lastLoginAt: { type: 'timestamp' },});
// ❌ Badconst User = Model('user', { uname: { type: 'string' }, verified: { type: 'bool' }, login: { type: 'timestamp' },});2. Always Hide Sensitive Fields
Section titled “2. Always Hide Sensitive Fields”const User = Model('user', { password: { type: 'string', required: true, select: false, // Never return passwords! },
apiKey: { type: 'string', select: false, // Hide API keys },});3. Use Enums for Fixed Values
Section titled “3. Use Enums for Fixed Values”// ✅ Good - type safeconst User = Model('user', { role: { type: 'enum', values: ['user', 'admin', 'moderator'], default: 'user', },});
// ❌ Bad - allows any stringconst User = Model('user', { role: { type: 'string', default: 'user', },});4. Set Reasonable Constraints
Section titled “4. Set Reasonable Constraints”const User = Model('user', { username: { type: 'string', min: 3, // Prevent single-character usernames max: 30, // Prevent extremely long usernames unique: true, },
age: { type: 'number', min: 13, // Minimum age requirement max: 120, // Reasonable maximum },});5. Use Timestamps
Section titled “5. Use Timestamps”const User = Model('user', { createdAt: { type: 'timestamp', default: 'now', },
updatedAt: { type: 'timestamp', default: 'now', },});6. Provide Defaults
Section titled “6. Provide Defaults”const User = Model('user', { role: { type: 'enum', values: ['user', 'admin'], default: 'user', // New users are 'user' by default },
isActive: { type: 'bool', default: true, // Active by default },
points: { type: 'number', default: 0, // Start with 0 points },});Common Patterns
Section titled “Common Patterns”User Model
Section titled “User Model”const User = Model('user', { username: { type: 'string', min: 3, max: 30, unique: true, required: true, }, email: { type: 'email', unique: true, required: true, }, password: { type: 'string', required: true, select: false, }, role: { type: 'enum', values: ['user', 'admin'], default: 'user', }, isActive: { type: 'bool', default: true, }, createdAt: { type: 'timestamp', default: 'now', },});Product Model
Section titled “Product Model”const Product = Model('products', { name: { type: 'string', required: true, min: 2, max: 200, }, description: { type: 'string', max: 1000, }, price: { type: 'number', required: true, min: 0, }, stock: { type: 'number', default: 0, min: 0, }, category: { type: 'enum', values: ['electronics', 'clothing', 'books', 'food'], required: true, }, isAvailable: { type: 'bool', default: true, }, createdAt: { type: 'timestamp', default: 'now', },});Post Model
Section titled “Post Model”const Post = Model('posts', { title: { type: 'string', required: true, min: 5, max: 200, }, slug: { type: 'string', unique: true, required: true, }, content: { type: 'string', required: true, }, author: { type: 'string', required: true, }, status: { type: 'enum', values: ['draft', 'published', 'archived'], default: 'draft', }, publishedAt: { type: 'timestamp', }, createdAt: { type: 'timestamp', default: 'now', },});Troubleshooting
Section titled “Troubleshooting”Validation Errors
Section titled “Validation Errors”Problem: Create/update fails with validation error
Check:
- Required fields are provided
- String lengths within min/max
- Number values within min/max
- Enum values are valid
- Email format is correct
Unique Constraint Errors
Section titled “Unique Constraint Errors”Problem: “Duplicate entry” error
Cause: Trying to insert duplicate value for unique field
Solution:
// Check if exists before creatingconst existing = await User.findOne({ email });if (existing) { return reply.status(badRequest()).json({ error: 'Email already registered', });}Field Not Returned
Section titled “Field Not Returned”Problem: Field missing in query results
Check:
// Is select: false?const users = await User.find(undefined,{ password: false});Next Steps
Section titled “Next Steps”Now that you understand models:
- Database Setup - Configure your database connection