Initial commit

This commit is contained in:
Zhongwei Li
2025-11-29 18:47:58 +08:00
commit 64a7404cc7
8 changed files with 2454 additions and 0 deletions

489
commands/sng-database.md Normal file
View File

@@ -0,0 +1,489 @@
# Database Configuration Command
You are helping the user configure database connections, optimize queries, and set up database-related infrastructure following Sngular's best practices.
## Instructions
1. **Determine the task type**:
- Initial database setup and connection
- Connection pool configuration
- Query optimization
- Migration setup
- Backup strategy
- Performance tuning
2. **Detect database and tools**:
- Database type (PostgreSQL, MySQL, MongoDB, etc.)
- ORM/Query builder (TypeORM, Prisma, Sequelize, etc.)
- Connection library
- Current project structure
3. **Ask for specific needs**:
- Development, staging, or production environment
- Connection pooling requirements
- Read replicas needed
- Caching strategy
- Monitoring requirements
## Implementation Tasks
### 1. Database Connection Setup
#### TypeORM Configuration
```typescript
// src/config/database.ts
import { DataSource } from 'typeorm'
import { config } from 'dotenv'
config()
export const AppDataSource = new DataSource({
type: 'postgres',
host: process.env.DB_HOST || 'localhost',
port: parseInt(process.env.DB_PORT || '5432'),
username: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
// Connection pool
extra: {
max: 20, // Maximum connections
min: 5, // Minimum connections
idleTimeoutMillis: 30000,
connectionTimeoutMillis: 2000,
},
// Entities
entities: ['src/entities/**/*.ts'],
migrations: ['src/migrations/**/*.ts'],
subscribers: ['src/subscribers/**/*.ts'],
// Development settings
synchronize: process.env.NODE_ENV === 'development',
logging: process.env.NODE_ENV === 'development' ? ['query', 'error'] : ['error'],
// Connection retry
retryAttempts: 10,
retryDelay: 3000,
// SSL for production
ssl: process.env.NODE_ENV === 'production' ? { rejectUnauthorized: false } : false,
})
// Initialize connection
export const initializeDatabase = async () => {
try {
await AppDataSource.initialize()
console.log('✅ Database connection established')
} catch (error) {
console.error('❌ Database connection failed:', error)
process.exit(1)
}
}
```
#### Prisma Configuration
```typescript
// src/config/database.ts
import { PrismaClient } from '@prisma/client'
const prismaClientSingleton = () => {
return new PrismaClient({
log: process.env.NODE_ENV === 'development' ? ['query', 'error', 'warn'] : ['error'],
datasources: {
db: {
url: process.env.DATABASE_URL,
},
},
})
}
declare global {
var prisma: undefined | ReturnType<typeof prismaClientSingleton>
}
export const prisma = globalThis.prisma ?? prismaClientSingleton()
if (process.env.NODE_ENV !== 'production') globalThis.prisma = prisma
// Graceful shutdown
export const disconnectDatabase = async () => {
await prisma.$disconnect()
}
// Health check
export const checkDatabaseConnection = async () => {
try {
await prisma.$queryRaw`SELECT 1`
return true
} catch (error) {
console.error('Database health check failed:', error)
return false
}
}
```
#### Mongoose (MongoDB) Configuration
```typescript
// src/config/database.ts
import mongoose from 'mongoose'
const MONGODB_URI = process.env.MONGODB_URI || 'mongodb://localhost:27017/myapp'
export const connectDatabase = async () => {
try {
await mongoose.connect(MONGODB_URI, {
maxPoolSize: 10,
minPoolSize: 5,
socketTimeoutMS: 45000,
serverSelectionTimeoutMS: 5000,
family: 4, // Use IPv4
})
console.log('✅ MongoDB connected')
// Connection events
mongoose.connection.on('error', (err) => {
console.error('MongoDB connection error:', err)
})
mongoose.connection.on('disconnected', () => {
console.log('MongoDB disconnected')
})
// Graceful shutdown
process.on('SIGINT', async () => {
await mongoose.connection.close()
process.exit(0)
})
} catch (error) {
console.error('Failed to connect to MongoDB:', error)
process.exit(1)
}
}
```
### 2. Environment Variables
```bash
# .env
# Database
DB_HOST=localhost
DB_PORT=5432
DB_USER=myapp_user
DB_PASSWORD=secure_password_here
DB_NAME=myapp_db
# Alternative: Full connection string
DATABASE_URL=postgresql://myapp_user:secure_password_here@localhost:5432/myapp_db
# MongoDB
MONGODB_URI=mongodb://localhost:27017/myapp
# Connection pool
DB_POOL_MIN=5
DB_POOL_MAX=20
# Production
NODE_ENV=production
DB_SSL=true
```
```bash
# .env.example (commit this to git)
DB_HOST=localhost
DB_PORT=5432
DB_USER=your_db_user
DB_PASSWORD=your_db_password
DB_NAME=your_db_name
DATABASE_URL=postgresql://user:password@host:port/database
```
### 3. Connection Pool Configuration
```typescript
// Optimized pool settings by environment
export const getPoolConfig = () => {
const env = process.env.NODE_ENV
if (env === 'production') {
return {
max: 20,
min: 10,
idleTimeoutMillis: 30000,
connectionTimeoutMillis: 2000,
}
} else if (env === 'staging') {
return {
max: 10,
min: 5,
idleTimeoutMillis: 30000,
connectionTimeoutMillis: 2000,
}
} else {
// development
return {
max: 5,
min: 2,
idleTimeoutMillis: 10000,
connectionTimeoutMillis: 2000,
}
}
}
```
### 4. Query Optimization
```typescript
// Bad: N+1 query problem
const users = await User.find()
for (const user of users) {
const posts = await Post.find({ authorId: user.id }) // N queries
}
// Good: Eager loading
const users = await User.find({
relations: ['posts'],
})
// Good: Join query
const users = await dataSource
.createQueryBuilder(User, 'user')
.leftJoinAndSelect('user.posts', 'post')
.getMany()
// Prisma with includes
const users = await prisma.user.findMany({
include: {
posts: true,
},
})
```
```typescript
// Use indexes effectively
const users = await User.find({
where: { email: 'test@example.com' }, // email column should be indexed
})
// Use select to fetch only needed fields
const users = await User.find({
select: ['id', 'email', 'name'], // Don't fetch all columns
})
// Pagination with cursors (better than offset)
const users = await User.find({
where: { id: MoreThan(lastSeenId) },
take: 20,
order: { id: 'ASC' },
})
```
### 5. Transactions
```typescript
// TypeORM transaction
await AppDataSource.transaction(async (manager) => {
const user = await manager.save(User, { email: 'test@example.com' })
await manager.save(Profile, { userId: user.id, bio: 'Hello' })
// Both saved or both rolled back
})
// Prisma transaction
await prisma.$transaction(async (tx) => {
const user = await tx.user.create({ data: { email: 'test@example.com' } })
await tx.profile.create({ data: { userId: user.id, bio: 'Hello' } })
})
// Prisma sequential operations
await prisma.$transaction([
prisma.user.create({ data: { email: 'test@example.com' } }),
prisma.post.create({ data: { title: 'First post' } }),
])
```
### 6. Database Migrations
```typescript
// Create migration script in package.json
{
"scripts": {
"migration:generate": "typeorm migration:generate -d src/config/database.ts src/migrations/Migration",
"migration:run": "typeorm migration:run -d src/config/database.ts",
"migration:revert": "typeorm migration:revert -d src/config/database.ts",
"schema:sync": "typeorm schema:sync -d src/config/database.ts",
"schema:drop": "typeorm schema:drop -d src/config/database.ts"
}
}
// Prisma migrations
{
"scripts": {
"prisma:migrate:dev": "prisma migrate dev",
"prisma:migrate:deploy": "prisma migrate deploy",
"prisma:migrate:reset": "prisma migrate reset",
"prisma:generate": "prisma generate",
"prisma:studio": "prisma studio"
}
}
```
### 7. Health Check Endpoint
```typescript
// src/routes/health.ts
import { Request, Response } from 'express'
import { AppDataSource } from '../config/database'
export const healthCheck = async (req: Request, res: Response) => {
try {
// Check database connection
await AppDataSource.query('SELECT 1')
res.status(200).json({
status: 'healthy',
database: 'connected',
timestamp: new Date().toISOString(),
})
} catch (error) {
res.status(503).json({
status: 'unhealthy',
database: 'disconnected',
error: error.message,
timestamp: new Date().toISOString(),
})
}
}
```
### 8. Database Seeding
```typescript
// src/seeds/seed.ts
import { AppDataSource } from '../config/database'
import { User } from '../entities/User'
import { Role } from '../entities/Role'
export const seedDatabase = async () => {
await AppDataSource.initialize()
// Create roles
const adminRole = await Role.create({ name: 'admin' }).save()
const userRole = await Role.create({ name: 'user' }).save()
// Create users
await User.create({
email: 'admin@example.com',
name: 'Admin User',
role: adminRole,
}).save()
await User.create({
email: 'user@example.com',
name: 'Regular User',
role: userRole,
}).save()
console.log('✅ Database seeded')
await AppDataSource.destroy()
}
// Run: ts-node src/seeds/seed.ts
if (require.main === module) {
seedDatabase().catch(console.error)
}
```
### 9. Query Logging and Monitoring
```typescript
// Custom query logger
import { Logger, QueryRunner } from 'typeorm'
export class DatabaseLogger implements Logger {
logQuery(query: string, parameters?: any[], queryRunner?: QueryRunner) {
console.log('Query:', query)
console.log('Parameters:', parameters)
}
logQueryError(error: string, query: string, parameters?: any[]) {
console.error('Query Error:', error)
console.error('Query:', query)
}
logQuerySlow(time: number, query: string, parameters?: any[]) {
console.warn(`Slow query (${time}ms):`, query)
}
logSchemaBuild(message: string) {
console.log('Schema Build:', message)
}
logMigration(message: string) {
console.log('Migration:', message)
}
log(level: 'log' | 'info' | 'warn', message: any) {
console.log(`[${level.toUpperCase()}]`, message)
}
}
```
### 10. Database Backup Script
```bash
#!/bin/bash
# scripts/backup-db.sh
# Load environment variables
source .env
# Create backup directory
mkdir -p backups
# Generate timestamp
TIMESTAMP=$(date +"%Y%m%d_%H%M%S")
# PostgreSQL backup
pg_dump -h $DB_HOST -U $DB_USER -d $DB_NAME > "backups/backup_${TIMESTAMP}.sql"
# Compress backup
gzip "backups/backup_${TIMESTAMP}.sql"
# Delete backups older than 30 days
find backups/ -name "*.gz" -mtime +30 -delete
echo "✅ Backup completed: backup_${TIMESTAMP}.sql.gz"
```
## Best Practices
1. **Always use connection pooling** - Reuse connections instead of creating new ones
2. **Use environment variables** - Never hardcode credentials
3. **Implement health checks** - Monitor database connectivity
4. **Use migrations** - Never modify database schema manually
5. **Index appropriately** - Index foreign keys and frequently queried columns
6. **Optimize queries** - Use explain plans to identify slow queries
7. **Use transactions** - For operations that must succeed or fail together
8. **Implement read replicas** - For high-read applications
9. **Set up monitoring** - Track query performance and connection pool metrics
10. **Regular backups** - Automate database backups
## Security Checklist
- [ ] Use SSL/TLS for database connections in production
- [ ] Store credentials in environment variables or secrets manager
- [ ] Use least privilege principle for database users
- [ ] Enable audit logging for sensitive operations
- [ ] Implement connection timeout and retry logic
- [ ] Validate and sanitize all inputs
- [ ] Use parameterized queries (prevent SQL injection)
- [ ] Regular security patches and updates
- [ ] Implement IP whitelisting for database access
- [ ] Enable database firewall rules
Ask the user: "What database configuration task would you like help with?"

318
commands/sng-endpoint.md Normal file
View File

@@ -0,0 +1,318 @@
# Create API Endpoint Command
You are helping the user create a new API endpoint following Sngular's backend development best practices.
## Instructions
1. **Detect the backend framework**:
- Node.js with Express
- Node.js with Fastify
- NestJS
- Python with FastAPI
- Python with Flask/Django
- Go with Gin/Echo
- Other framework
2. **Ask for endpoint details**:
- HTTP method (GET, POST, PUT, PATCH, DELETE)
- Route path (e.g., `/api/users`, `/api/posts/:id`)
- Purpose and description
- Request body schema (if applicable)
- Response schema
- Authentication required (yes/no)
- Rate limiting needed (yes/no)
3. **Determine API style**:
- REST API
- GraphQL (query/mutation/subscription)
- gRPC
- WebSocket
## Implementation Tasks
### For REST Endpoints:
1. **Create route handler** with:
- HTTP method and path
- Request validation middleware
- Business logic / controller method
- Response formatting
- Error handling
2. **Add request validation**:
- Query parameters validation
- Path parameters validation
- Request body validation (using Zod, Joi, class-validator)
- File upload validation (if needed)
3. **Implement authentication/authorization**:
- JWT token verification
- Role-based access control (RBAC)
- Permission checks
- API key validation
4. **Add error handling**:
- Try-catch blocks
- Custom error classes
- HTTP status codes
- Error response formatting
5. **Create tests**:
- Unit tests for controller logic
- Integration tests for full endpoint
- Mock database/external services
- Test authentication flows
6. **Add documentation**:
- OpenAPI/Swagger annotations
- JSDoc/docstrings
- Request/response examples
- Error codes documentation
### For GraphQL:
1. **Define schema**:
- Type definitions
- Input types
- Custom scalars
2. **Create resolver**:
- Query/Mutation/Subscription resolver
- Field resolvers
- DataLoader for N+1 prevention
3. **Add validation & auth**:
- Schema directives
- Resolver-level authorization
- Input validation
## Files to Create/Update
1. **Route/Controller file**: Define the endpoint handler
2. **Validation schema**: Request/response validation
3. **Service layer**: Business logic (separate from controller)
4. **Tests**: Comprehensive endpoint testing
5. **Types/Interfaces**: TypeScript types or Pydantic models
6. **Documentation**: API docs/Swagger definitions
## Best Practices to Follow
### Code Structure
```
src/
├── routes/
│ └── users.routes.ts # Route definitions
├── controllers/
│ └── users.controller.ts # Request handlers
├── services/
│ └── users.service.ts # Business logic
├── validators/
│ └── users.validator.ts # Input validation
├── types/
│ └── users.types.ts # TypeScript types
└── tests/
└── users.test.ts # Endpoint tests
```
### Request Validation
```typescript
import { z } from 'zod'
const CreateUserSchema = z.object({
email: z.string().email(),
name: z.string().min(2).max(100),
age: z.number().int().positive().optional(),
})
```
### Error Handling
```typescript
// Custom error classes
class BadRequestError extends Error {
statusCode = 400
}
class UnauthorizedError extends Error {
statusCode = 401
}
// Error handling middleware
app.use((err, req, res, next) => {
res.status(err.statusCode || 500).json({
error: {
message: err.message,
code: err.code,
},
})
})
```
### Authentication
```typescript
// JWT middleware
const authMiddleware = async (req, res, next) => {
const token = req.headers.authorization?.split(' ')[1]
if (!token) {
throw new UnauthorizedError('No token provided')
}
const decoded = jwt.verify(token, process.env.JWT_SECRET)
req.user = decoded
next()
}
```
### Rate Limiting
```typescript
import rateLimit from 'express-rate-limit'
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // limit each IP to 100 requests per windowMs
})
app.use('/api/', limiter)
```
### Response Formatting
```typescript
// Success response
res.status(200).json({
success: true,
data: result,
meta: {
page: 1,
limit: 20,
total: 100,
},
})
// Error response
res.status(400).json({
success: false,
error: {
code: 'VALIDATION_ERROR',
message: 'Invalid email format',
details: validationErrors,
},
})
```
### Database Operations
```typescript
// Use transactions for multiple operations
await db.transaction(async (trx) => {
const user = await trx('users').insert(userData)
await trx('profiles').insert({ user_id: user.id, ...profileData })
})
```
### Logging
```typescript
import logger from './utils/logger'
app.post('/api/users', async (req, res) => {
logger.info('Creating new user', { email: req.body.email })
try {
const user = await createUser(req.body)
logger.info('User created successfully', { userId: user.id })
res.status(201).json({ data: user })
} catch (error) {
logger.error('Failed to create user', { error, body: req.body })
throw error
}
})
```
## Testing Example
```typescript
import request from 'supertest'
import app from '../app'
describe('POST /api/users', () => {
it('creates a new user with valid data', async () => {
const response = await request(app)
.post('/api/users')
.send({
email: 'test@example.com',
name: 'Test User',
})
.expect(201)
expect(response.body.data).toHaveProperty('id')
expect(response.body.data.email).toBe('test@example.com')
})
it('returns 400 for invalid email', async () => {
await request(app)
.post('/api/users')
.send({
email: 'invalid-email',
name: 'Test User',
})
.expect(400)
})
it('requires authentication', async () => {
await request(app)
.post('/api/users')
.send({ email: 'test@example.com' })
.expect(401)
})
})
```
## OpenAPI/Swagger Documentation
```typescript
/**
* @swagger
* /api/users:
* post:
* summary: Create a new user
* tags: [Users]
* security:
* - bearerAuth: []
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* required:
* - email
* - name
* properties:
* email:
* type: string
* format: email
* name:
* type: string
* responses:
* 201:
* description: User created successfully
* 400:
* description: Invalid input
* 401:
* description: Unauthorized
*/
```
## Security Considerations
- Always validate and sanitize input
- Use parameterized queries to prevent SQL injection
- Implement rate limiting
- Use HTTPS in production
- Never expose sensitive data in responses
- Hash passwords with bcrypt
- Implement CORS properly
- Use security headers (helmet.js)
- Validate JWT tokens properly
- Implement proper session management
Ask the user: "What API endpoint would you like to create?"

455
commands/sng-model.md Normal file
View File

@@ -0,0 +1,455 @@
# Create Database Model Command
You are helping the user create a database model with proper relationships, validation, and migrations following Sngular's backend best practices.
## Instructions
1. **Detect the ORM/database tool**:
- TypeORM (TypeScript/Node.js)
- Prisma (TypeScript/Node.js)
- Sequelize (JavaScript/TypeScript)
- Mongoose (MongoDB)
- SQLAlchemy (Python)
- Django ORM (Python)
- GORM (Go)
- Other
2. **Determine database type**:
- PostgreSQL
- MySQL/MariaDB
- MongoDB
- SQLite
- SQL Server
- Other
3. **Ask for model details**:
- Model name (e.g., User, Product, Order)
- Fields/attributes with types
- Validation rules
- Relationships to other models
- Indexes needed
- Timestamps (created_at, updated_at)
- Soft deletes needed
4. **Identify relationships**:
- One-to-One
- One-to-Many
- Many-to-Many
- Self-referential
## Implementation Tasks
### 1. Create Model Class/Schema
```typescript
// TypeORM Example
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn, ManyToOne, OneToMany } from 'typeorm'
@Entity('users')
export class User {
@PrimaryGeneratedColumn('uuid')
id: string
@Column({ unique: true })
email: string
@Column()
name: string
@Column({ nullable: true })
avatar?: string
@Column({ default: true })
isActive: boolean
@CreateDateColumn()
createdAt: Date
@UpdateDateColumn()
updatedAt: Date
// Relationships
@OneToMany(() => Post, post => post.author)
posts: Post[]
@ManyToOne(() => Role, role => role.users)
role: Role
}
```
### 2. Add Validation
```typescript
import { IsEmail, IsString, MinLength, MaxLength, IsOptional } from 'class-validator'
export class CreateUserDto {
@IsEmail()
email: string
@IsString()
@MinLength(2)
@MaxLength(100)
name: string
@IsString()
@IsOptional()
avatar?: string
}
```
### 3. Create Migration
```typescript
// TypeORM Migration
import { MigrationInterface, QueryRunner, Table, TableForeignKey } from 'typeorm'
export class CreateUsersTable1234567890 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.createTable(
new Table({
name: 'users',
columns: [
{
name: 'id',
type: 'uuid',
isPrimary: true,
generationStrategy: 'uuid',
default: 'uuid_generate_v4()',
},
{
name: 'email',
type: 'varchar',
isUnique: true,
},
{
name: 'name',
type: 'varchar',
},
{
name: 'avatar',
type: 'varchar',
isNullable: true,
},
{
name: 'is_active',
type: 'boolean',
default: true,
},
{
name: 'role_id',
type: 'uuid',
isNullable: true,
},
{
name: 'created_at',
type: 'timestamp',
default: 'now()',
},
{
name: 'updated_at',
type: 'timestamp',
default: 'now()',
},
],
}),
true,
)
// Add foreign key
await queryRunner.createForeignKey(
'users',
new TableForeignKey({
columnNames: ['role_id'],
referencedColumnNames: ['id'],
referencedTableName: 'roles',
onDelete: 'SET NULL',
}),
)
// Add indexes
await queryRunner.createIndex('users', {
name: 'IDX_USER_EMAIL',
columnNames: ['email'],
})
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.dropTable('users')
}
}
```
### 4. Create Repository/Service
```typescript
// Repository pattern
import { Repository } from 'typeorm'
import { User } from './user.entity'
export class UserRepository extends Repository<User> {
async findByEmail(email: string): Promise<User | null> {
return this.findOne({ where: { email } })
}
async findActiveUsers(): Promise<User[]> {
return this.find({
where: { isActive: true },
relations: ['role', 'posts'],
})
}
async createUser(data: CreateUserDto): Promise<User> {
const user = this.create(data)
return this.save(user)
}
}
```
## Prisma Example
```typescript
// schema.prisma
model User {
id String @id @default(uuid())
email String @unique
name String
avatar String?
isActive Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
// Relations
posts Post[]
role Role? @relation(fields: [roleId], references: [id])
roleId String?
@@index([email])
@@map("users")
}
model Post {
id String @id @default(uuid())
title String
content String
published Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
author User @relation(fields: [authorId], references: [id])
authorId String
@@map("posts")
}
model Role {
id String @id @default(uuid())
name String @unique
users User[]
@@map("roles")
}
```
## Mongoose Example (MongoDB)
```typescript
import mongoose, { Schema, Document } from 'mongoose'
export interface IUser extends Document {
email: string
name: string
avatar?: string
isActive: boolean
roleId?: mongoose.Types.ObjectId
createdAt: Date
updatedAt: Date
}
const UserSchema = new Schema<IUser>(
{
email: {
type: String,
required: true,
unique: true,
lowercase: true,
trim: true,
validate: {
validator: (v: string) => /\S+@\S+\.\S+/.test(v),
message: 'Invalid email format',
},
},
name: {
type: String,
required: true,
minlength: 2,
maxlength: 100,
},
avatar: {
type: String,
},
isActive: {
type: Boolean,
default: true,
},
roleId: {
type: Schema.Types.ObjectId,
ref: 'Role',
},
},
{
timestamps: true,
},
)
// Indexes
UserSchema.index({ email: 1 })
UserSchema.index({ isActive: 1, createdAt: -1 })
// Virtual populate
UserSchema.virtual('posts', {
ref: 'Post',
localField: '_id',
foreignField: 'authorId',
})
// Methods
UserSchema.methods.toJSON = function () {
const obj = this.toObject()
delete obj.__v
return obj
}
export const User = mongoose.model<IUser>('User', UserSchema)
```
## Best Practices
### 1. Naming Conventions
- Use singular names for models (User, not Users)
- Use camelCase for field names in code
- Use snake_case for database column names
- Prefix foreign keys with table name (user_id, not just id)
### 2. Data Types
- Use UUID for primary keys
- Use ENUM for fixed sets of values
- Use appropriate numeric types (int, bigint, decimal)
- Use TEXT for unlimited length strings
- Use JSONB for flexible data (PostgreSQL)
### 3. Relationships
- Always define both sides of relationships
- Use appropriate cascade options (CASCADE, SET NULL, RESTRICT)
- Index foreign key columns
- Consider soft deletes for important data
### 4. Indexes
- Index columns used in WHERE clauses
- Index foreign key columns
- Create composite indexes for multi-column queries
- Don't over-index (impacts write performance)
### 5. Validation
- Validate at both model and database level
- Use appropriate constraints (NOT NULL, UNIQUE, CHECK)
- Validate data types and formats
- Implement custom validators for complex rules
### 6. Timestamps
- Always include created_at and updated_at
- Consider deleted_at for soft deletes
- Use database-level defaults (now())
### 7. Security
- Never store passwords in plain text
- Hash sensitive data
- Use appropriate field types for sensitive data
- Implement row-level security where needed
## Files to Create
1. **Entity/Model file**: Model definition
2. **DTO files**: Data transfer objects for validation
3. **Migration file**: Database schema changes
4. **Repository file**: Data access methods (if applicable)
5. **Seed file**: Sample data for development/testing
6. **Tests**: Model and repository tests
## Testing Example
```typescript
import { User } from './user.entity'
import { AppDataSource } from './data-source'
describe('User Model', () => {
beforeAll(async () => {
await AppDataSource.initialize()
})
afterAll(async () => {
await AppDataSource.destroy()
})
it('creates a user with valid data', async () => {
const user = User.create({
email: 'test@example.com',
name: 'Test User',
})
await user.save()
expect(user.id).toBeDefined()
expect(user.email).toBe('test@example.com')
expect(user.createdAt).toBeInstanceOf(Date)
})
it('enforces unique email constraint', async () => {
await User.create({ email: 'duplicate@example.com', name: 'User 1' }).save()
await expect(
User.create({ email: 'duplicate@example.com', name: 'User 2' }).save()
).rejects.toThrow()
})
it('validates email format', async () => {
const user = User.create({ email: 'invalid-email', name: 'Test User' })
await expect(user.save()).rejects.toThrow()
})
})
```
## Common Relationship Patterns
### One-to-Many
```typescript
// One user has many posts
@OneToMany(() => Post, post => post.author)
posts: Post[]
@ManyToOne(() => User, user => user.posts)
author: User
```
### Many-to-Many
```typescript
// Users can have many roles, roles can have many users
@ManyToMany(() => Role, role => role.users)
@JoinTable({ name: 'user_roles' })
roles: Role[]
@ManyToMany(() => User, user => user.roles)
users: User[]
```
### Self-Referential
```typescript
// User can have a manager who is also a User
@ManyToOne(() => User, user => user.subordinates)
manager: User
@OneToMany(() => User, user => user.manager)
subordinates: User[]
```
Ask the user: "What database model would you like to create?"