557 lines
13 KiB
Markdown
557 lines
13 KiB
Markdown
# Advanced Backend Features
|
|
|
|
This reference provides detailed implementation guidance for advanced backend features beyond basic CRUD operations.
|
|
|
|
## Multi-Tenant Architecture
|
|
|
|
### When to Use
|
|
When workflow indicates multiple organizations/tenants need to share the same application instance with data isolation.
|
|
|
|
### Implementation Strategy
|
|
|
|
#### 1. Database Schema Modifications
|
|
Add tenant context to all tables:
|
|
```sql
|
|
ALTER TABLE users ADD COLUMN tenant_id UUID NOT NULL;
|
|
ALTER TABLE posts ADD COLUMN tenant_id UUID NOT NULL;
|
|
ALTER TABLE products ADD COLUMN tenant_id UUID NOT NULL;
|
|
|
|
-- Create index for performance
|
|
CREATE INDEX idx_users_tenant ON users(tenant_id);
|
|
CREATE INDEX idx_posts_tenant ON posts(tenant_id);
|
|
```
|
|
|
|
#### 2. Tenant Identification Middleware
|
|
```javascript
|
|
// Express.js
|
|
const tenantMiddleware = async (req, res, next) => {
|
|
// Extract tenant from subdomain, header, or token
|
|
const tenant = req.subdomains[0] ||
|
|
req.headers['x-tenant-id'] ||
|
|
req.user?.tenantId;
|
|
|
|
if (!tenant) {
|
|
return res.status(400).json({ error: 'Tenant not specified' });
|
|
}
|
|
|
|
req.tenant = await Tenant.findById(tenant);
|
|
next();
|
|
};
|
|
```
|
|
|
|
#### 3. Tenant-Scoped Queries
|
|
```javascript
|
|
// Automatically scope all queries to current tenant
|
|
class TenantModel {
|
|
static async find(query) {
|
|
return db.query({
|
|
...query,
|
|
tenant_id: req.tenant.id
|
|
});
|
|
}
|
|
}
|
|
```
|
|
|
|
#### 4. Tenant Management API
|
|
Generate endpoints for:
|
|
- `POST /api/tenants` - Create new tenant
|
|
- `GET /api/tenants/:id` - Get tenant info
|
|
- `PUT /api/tenants/:id/settings` - Update settings
|
|
- `GET /api/tenants/:id/usage` - Usage statistics
|
|
|
|
## Real-Time Features
|
|
|
|
### When to Use
|
|
When frontend has WebSocket connections, live updates, chat, notifications, or collaborative editing.
|
|
|
|
### Implementation Options
|
|
|
|
#### Option 1: Socket.IO (Recommended for Node.js)
|
|
|
|
**Setup:**
|
|
```javascript
|
|
const io = require('socket.io')(server, {
|
|
cors: { origin: process.env.FRONTEND_URL }
|
|
});
|
|
|
|
// Authentication
|
|
io.use(async (socket, next) => {
|
|
const token = socket.handshake.auth.token;
|
|
const user = await verifyToken(token);
|
|
socket.user = user;
|
|
next();
|
|
});
|
|
|
|
// Handle connections
|
|
io.on('connection', (socket) => {
|
|
console.log(`User ${socket.user.id} connected`);
|
|
|
|
// Join user's room
|
|
socket.join(`user:${socket.user.id}`);
|
|
|
|
// Handle events
|
|
socket.on('message', async (data) => {
|
|
await Message.create({ ...data, userId: socket.user.id });
|
|
io.to(`room:${data.roomId}`).emit('message', data);
|
|
});
|
|
});
|
|
```
|
|
|
|
**Patterns:**
|
|
```javascript
|
|
// Broadcast to specific user
|
|
io.to(`user:${userId}`).emit('notification', data);
|
|
|
|
// Broadcast to room
|
|
io.to(`room:${roomId}`).emit('message', data);
|
|
|
|
// Broadcast to all except sender
|
|
socket.broadcast.emit('user-joined', socket.user);
|
|
```
|
|
|
|
#### Option 2: Server-Sent Events (SSE)
|
|
|
|
**For one-way updates:**
|
|
```javascript
|
|
app.get('/api/events', (req, res) => {
|
|
res.setHeader('Content-Type', 'text/event-stream');
|
|
res.setHeader('Cache-Control', 'no-cache');
|
|
res.setHeader('Connection', 'keep-alive');
|
|
|
|
const sendEvent = (data) => {
|
|
res.write(`data: ${JSON.stringify(data)}\n\n`);
|
|
};
|
|
|
|
// Subscribe to events
|
|
eventEmitter.on('update', sendEvent);
|
|
|
|
req.on('close', () => {
|
|
eventEmitter.off('update', sendEvent);
|
|
});
|
|
});
|
|
```
|
|
|
|
#### Option 3: WebSocket (Native)
|
|
|
|
**For Python FastAPI:**
|
|
```python
|
|
from fastapi import WebSocket
|
|
|
|
@app.websocket("/ws")
|
|
async def websocket_endpoint(websocket: WebSocket):
|
|
await websocket.accept()
|
|
try:
|
|
while True:
|
|
data = await websocket.receive_text()
|
|
await websocket.send_text(f"Echo: {data}")
|
|
except WebSocketDisconnect:
|
|
print("Client disconnected")
|
|
```
|
|
|
|
### Connection Management
|
|
|
|
**Track active connections:**
|
|
```javascript
|
|
const activeConnections = new Map();
|
|
|
|
io.on('connection', (socket) => {
|
|
activeConnections.set(socket.user.id, socket);
|
|
|
|
socket.on('disconnect', () => {
|
|
activeConnections.delete(socket.user.id);
|
|
});
|
|
});
|
|
|
|
// Send to specific user
|
|
function notifyUser(userId, event, data) {
|
|
const socket = activeConnections.get(userId);
|
|
if (socket) {
|
|
socket.emit(event, data);
|
|
}
|
|
}
|
|
```
|
|
|
|
## File Upload Handling
|
|
|
|
### When to Use
|
|
When frontend has file upload forms, image uploads, document attachments, or bulk imports.
|
|
|
|
### Implementation Strategy
|
|
|
|
#### 1. Basic Upload Handler
|
|
|
|
**Express with Multer:**
|
|
```javascript
|
|
const multer = require('multer');
|
|
const path = require('path');
|
|
|
|
const storage = multer.diskStorage({
|
|
destination: './uploads/',
|
|
filename: (req, file, cb) => {
|
|
const uniqueName = `${Date.now()}-${file.originalname}`;
|
|
cb(null, uniqueName);
|
|
}
|
|
});
|
|
|
|
const upload = multer({
|
|
storage: storage,
|
|
limits: { fileSize: 5 * 1024 * 1024 }, // 5MB
|
|
fileFilter: (req, file, cb) => {
|
|
const allowedTypes = /jpeg|jpg|png|pdf/;
|
|
const extname = allowedTypes.test(path.extname(file.originalname).toLowerCase());
|
|
const mimetype = allowedTypes.test(file.mimetype);
|
|
|
|
if (extname && mimetype) {
|
|
cb(null, true);
|
|
} else {
|
|
cb(new Error('Invalid file type'));
|
|
}
|
|
}
|
|
});
|
|
|
|
app.post('/api/upload', upload.single('file'), async (req, res) => {
|
|
const fileRecord = await File.create({
|
|
filename: req.file.filename,
|
|
originalName: req.file.originalname,
|
|
size: req.file.size,
|
|
mimetype: req.file.mimetype,
|
|
userId: req.user.id
|
|
});
|
|
|
|
res.json({ success: true, file: fileRecord });
|
|
});
|
|
```
|
|
|
|
#### 2. Cloud Storage (AWS S3)
|
|
|
|
**Direct upload with presigned URLs:**
|
|
```javascript
|
|
const AWS = require('aws-sdk');
|
|
const s3 = new AWS.S3();
|
|
|
|
app.post('/api/upload/presign', async (req, res) => {
|
|
const { filename, contentType } = req.body;
|
|
const key = `uploads/${req.user.id}/${Date.now()}-${filename}`;
|
|
|
|
const presignedUrl = s3.getSignedUrl('putObject', {
|
|
Bucket: process.env.S3_BUCKET,
|
|
Key: key,
|
|
ContentType: contentType,
|
|
Expires: 300 // 5 minutes
|
|
});
|
|
|
|
res.json({
|
|
uploadUrl: presignedUrl,
|
|
fileKey: key
|
|
});
|
|
});
|
|
|
|
// After client uploads to S3
|
|
app.post('/api/upload/confirm', async (req, res) => {
|
|
const { fileKey } = req.body;
|
|
|
|
const fileUrl = `https://${process.env.S3_BUCKET}.s3.amazonaws.com/${fileKey}`;
|
|
|
|
await File.create({
|
|
key: fileKey,
|
|
url: fileUrl,
|
|
userId: req.user.id
|
|
});
|
|
|
|
res.json({ success: true, url: fileUrl });
|
|
});
|
|
```
|
|
|
|
#### 3. Image Processing
|
|
|
|
**Resize and optimize:**
|
|
```javascript
|
|
const sharp = require('sharp');
|
|
|
|
app.post('/api/upload/image', upload.single('image'), async (req, res) => {
|
|
const processedPath = `processed-${req.file.filename}`;
|
|
|
|
await sharp(req.file.path)
|
|
.resize(800, 800, { fit: 'inside' })
|
|
.jpeg({ quality: 80 })
|
|
.toFile(path.join('./uploads/', processedPath));
|
|
|
|
res.json({
|
|
original: req.file.filename,
|
|
processed: processedPath
|
|
});
|
|
});
|
|
```
|
|
|
|
#### 4. Multiple File Upload
|
|
|
|
```javascript
|
|
app.post('/api/upload/multiple', upload.array('files', 10), async (req, res) => {
|
|
const files = await Promise.all(
|
|
req.files.map(file => File.create({
|
|
filename: file.filename,
|
|
originalName: file.originalname,
|
|
size: file.size,
|
|
userId: req.user.id
|
|
}))
|
|
);
|
|
|
|
res.json({ success: true, files });
|
|
});
|
|
```
|
|
|
|
### Security Considerations
|
|
|
|
1. **Validate file types** - Check both extension and MIME type
|
|
2. **Limit file size** - Prevent DoS attacks
|
|
3. **Scan for malware** - Use ClamAV or similar
|
|
4. **Generate unique filenames** - Prevent overwrites
|
|
5. **Store outside web root** - Prevent direct access
|
|
6. **Implement rate limiting** - Limit uploads per user
|
|
|
|
## Background Jobs
|
|
|
|
### When to Use
|
|
When workflow has async operations: email sending, report generation, data processing, scheduled tasks.
|
|
|
|
### Implementation Options
|
|
|
|
#### Option 1: Bull (Node.js with Redis)
|
|
|
|
**Setup:**
|
|
```javascript
|
|
const Queue = require('bull');
|
|
|
|
const emailQueue = new Queue('email', {
|
|
redis: {
|
|
host: process.env.REDIS_HOST,
|
|
port: process.env.REDIS_PORT
|
|
}
|
|
});
|
|
|
|
// Add job to queue
|
|
app.post('/api/send-email', async (req, res) => {
|
|
await emailQueue.add({
|
|
to: req.body.email,
|
|
subject: 'Welcome',
|
|
body: 'Thank you for signing up'
|
|
});
|
|
|
|
res.json({ success: true, message: 'Email queued' });
|
|
});
|
|
|
|
// Process jobs
|
|
emailQueue.process(async (job) => {
|
|
const { to, subject, body } = job.data;
|
|
await sendEmail(to, subject, body);
|
|
});
|
|
|
|
// Handle job events
|
|
emailQueue.on('completed', (job) => {
|
|
console.log(`Job ${job.id} completed`);
|
|
});
|
|
|
|
emailQueue.on('failed', (job, err) => {
|
|
console.error(`Job ${job.id} failed:`, err);
|
|
});
|
|
```
|
|
|
|
**Job priorities and delays:**
|
|
```javascript
|
|
// High priority
|
|
await emailQueue.add(data, { priority: 1 });
|
|
|
|
// Delayed job
|
|
await emailQueue.add(data, { delay: 60000 }); // 1 minute
|
|
|
|
// Scheduled job
|
|
await emailQueue.add(data, {
|
|
repeat: { cron: '0 9 * * *' } // Daily at 9 AM
|
|
});
|
|
```
|
|
|
|
#### Option 2: Celery (Python)
|
|
|
|
**Setup:**
|
|
```python
|
|
from celery import Celery
|
|
|
|
app = Celery('tasks', broker='redis://localhost:6379')
|
|
|
|
@app.task
|
|
def send_email(to, subject, body):
|
|
# Send email logic
|
|
pass
|
|
|
|
# Call async
|
|
send_email.delay('user@example.com', 'Hello', 'Welcome')
|
|
|
|
# Call with delay
|
|
send_email.apply_async(
|
|
args=['user@example.com', 'Hello', 'Welcome'],
|
|
countdown=60 # 60 seconds
|
|
)
|
|
|
|
# Periodic task
|
|
from celery.schedules import crontab
|
|
|
|
@app.task
|
|
def cleanup_old_files():
|
|
# Cleanup logic
|
|
pass
|
|
|
|
app.conf.beat_schedule = {
|
|
'cleanup-daily': {
|
|
'task': 'cleanup_old_files',
|
|
'schedule': crontab(hour=2, minute=0)
|
|
}
|
|
}
|
|
```
|
|
|
|
### Common Job Patterns
|
|
|
|
#### 1. Email Notifications
|
|
```javascript
|
|
const sendWelcomeEmail = async (userId) => {
|
|
await emailQueue.add({ userId, template: 'welcome' });
|
|
};
|
|
|
|
const sendDigest = async () => {
|
|
const users = await User.findActiveSubscribers();
|
|
for (const user of users) {
|
|
await emailQueue.add({
|
|
userId: user.id,
|
|
template: 'digest'
|
|
});
|
|
}
|
|
};
|
|
```
|
|
|
|
#### 2. Report Generation
|
|
```javascript
|
|
const generateReport = async (reportId) => {
|
|
await reportQueue.add({ reportId }, {
|
|
timeout: 300000, // 5 minutes
|
|
attempts: 3
|
|
});
|
|
};
|
|
|
|
reportQueue.process(async (job) => {
|
|
const { reportId } = job.data;
|
|
const data = await fetchReportData(reportId);
|
|
const pdf = await generatePDF(data);
|
|
await uploadToS3(pdf, reportId);
|
|
await Report.update(reportId, { status: 'completed' });
|
|
});
|
|
```
|
|
|
|
#### 3. Data Import
|
|
```javascript
|
|
const importCSV = async (fileId) => {
|
|
await importQueue.add({ fileId }, {
|
|
attempts: 1, // Don't retry
|
|
timeout: 600000 // 10 minutes
|
|
});
|
|
};
|
|
|
|
importQueue.process(async (job) => {
|
|
const { fileId } = job.data;
|
|
const file = await getFile(fileId);
|
|
const rows = await parseCSV(file);
|
|
|
|
for (let i = 0; i < rows.length; i++) {
|
|
await importRow(rows[i]);
|
|
job.progress((i / rows.length) * 100);
|
|
}
|
|
});
|
|
```
|
|
|
|
#### 4. Scheduled Tasks
|
|
```javascript
|
|
// Daily cleanup
|
|
const cleanupQueue = new Queue('cleanup');
|
|
|
|
cleanupQueue.add({}, {
|
|
repeat: { cron: '0 2 * * *' } // 2 AM daily
|
|
});
|
|
|
|
cleanupQueue.process(async () => {
|
|
await cleanupExpiredSessions();
|
|
await deleteOldLogs();
|
|
await optimizeDatabase();
|
|
});
|
|
```
|
|
|
|
### Monitoring and Debugging
|
|
|
|
**Job status endpoint:**
|
|
```javascript
|
|
app.get('/api/jobs/:id', async (req, res) => {
|
|
const job = await emailQueue.getJob(req.params.id);
|
|
|
|
res.json({
|
|
id: job.id,
|
|
progress: job.progress(),
|
|
state: await job.getState(),
|
|
failedReason: job.failedReason,
|
|
finishedOn: job.finishedOn
|
|
});
|
|
});
|
|
|
|
// Queue statistics
|
|
app.get('/api/queue/stats', async (req, res) => {
|
|
const [waiting, active, completed, failed] = await Promise.all([
|
|
emailQueue.getWaitingCount(),
|
|
emailQueue.getActiveCount(),
|
|
emailQueue.getCompletedCount(),
|
|
emailQueue.getFailedCount()
|
|
]);
|
|
|
|
res.json({ waiting, active, completed, failed });
|
|
});
|
|
```
|
|
|
|
### Error Handling
|
|
|
|
```javascript
|
|
emailQueue.process(async (job) => {
|
|
try {
|
|
await sendEmail(job.data);
|
|
} catch (error) {
|
|
// Log error
|
|
console.error(`Job ${job.id} failed:`, error);
|
|
|
|
// Notify admin on critical errors
|
|
if (error.code === 'SMTP_ERROR') {
|
|
await notifyAdmin(`Email system down: ${error.message}`);
|
|
}
|
|
|
|
throw error; // Re-throw to mark job as failed
|
|
}
|
|
});
|
|
|
|
// Retry failed jobs
|
|
emailQueue.on('failed', async (job, err) => {
|
|
if (job.attemptsMade < 3) {
|
|
await job.retry();
|
|
}
|
|
});
|
|
```
|
|
|
|
## Best Practices Summary
|
|
|
|
1. **Multi-Tenant**: Always validate tenant context, use indexes, implement audit logging
|
|
2. **Real-Time**: Handle disconnections gracefully, implement reconnection logic, validate all events
|
|
3. **File Upload**: Validate types and sizes, use virus scanning, implement cleanup jobs
|
|
4. **Background Jobs**: Set appropriate timeouts, implement retry logic, monitor queue health
|
|
|
|
## Integration with Main Workflow
|
|
|
|
When generating backend code, detect these patterns in frontend/UI:
|
|
- Multiple organization selector → Multi-tenant
|
|
- Live chat/notifications → Real-time
|
|
- File upload forms → File handling
|
|
- "Process in background" → Background jobs
|
|
|
|
Auto-generate appropriate infrastructure and endpoints for detected patterns.
|