Examples
Real-world examples and implementation patterns for SimplyStack
Overview
This page contains practical examples and implementation patterns for common use cases with SimplyStack. Each example includes complete code that you can copy and adapt for your projects.
💡 Getting the Most from Examples
- • All examples assume you have a valid API key
- • Error handling is included in each example
- • TypeScript types are used throughout
- • Examples are production-ready patterns
Blog Management System
A complete blog management system with content creation, SEO optimization, and publishing workflows.
Complete Blog Manager Class
// blog-manager.ts
import { SimplyStackSDK } from "@simplystack-org/sdk";
import type { BlogPost } from "@simplystack-org/sdk";
interface BlogDraft {
title: string;
content: string;
excerpt?: string;
tags?: string[];
seoData?: {
meta_title?: string;
meta_description?: string;
meta_keywords?: string[];
canonical_url?: string;
};
}
class BlogManager {
private sdk: SimplyStackSDK;
constructor(apiKey: string) {
this.sdk = new SimplyStackSDK(apiKey);
}
// Create a draft blog post
async createDraft(draft: BlogDraft): Promise<BlogPost> {
const { data: post, error } = await this.sdk.createBlogPost({
title: draft.title,
content: draft.content,
excerpt: draft.excerpt || this.extractExcerpt(draft.content),
tags: draft.tags || [],
status: "draft",
...draft.seoData,
});
if (error) {
throw new Error(`Failed to create draft: ${error}`);
}
return post!;
}
// Publish a blog post with SEO optimization
async publishPost(draft: BlogDraft): Promise<BlogPost> {
// Auto-generate SEO if not provided
const seoData = {
meta_title: draft.seoData?.meta_title || `${draft.title} | Your Blog`,
meta_description: draft.seoData?.meta_description ||
this.extractExcerpt(draft.content, 150),
meta_keywords: draft.seoData?.meta_keywords ||
this.extractKeywords(draft.content, draft.tags),
canonical_url: draft.seoData?.canonical_url,
};
const { data: post, error } = await this.sdk.createBlogPost({
title: draft.title,
content: draft.content,
excerpt: draft.excerpt || this.extractExcerpt(draft.content),
tags: draft.tags || [],
status: "published",
...seoData,
});
if (error) {
throw new Error(`Failed to publish post: ${error}`);
}
// Log the publication
await this.logPostAction("published", post!.id, {
title: post!.title,
tags: post!.tags,
});
return post!;
}
// Get all drafts
async getDrafts(): Promise<BlogPost[]> {
const { data: posts, error } = await this.sdk.getBlogPosts({
status: "draft",
});
if (error) {
throw new Error(`Failed to fetch drafts: ${error}`);
}
return posts || [];
}
// Get published posts with pagination
async getPublishedPosts(page = 1, limit = 10): Promise<{
posts: BlogPost[];
hasMore: boolean;
}> {
const { data: posts, error } = await this.sdk.getBlogPosts({
status: "published",
limit: limit + 1, // Get one extra to check if there are more
offset: (page - 1) * limit,
});
if (error) {
throw new Error(`Failed to fetch published posts: ${error}`);
}
const hasMore = (posts?.length || 0) > limit;
const actualPosts = hasMore ? posts!.slice(0, -1) : (posts || []);
return {
posts: actualPosts,
hasMore,
};
}
// Search posts by tags
async getPostsByTag(tag: string): Promise<BlogPost[]> {
const { data: posts, error } = await this.sdk.getBlogPosts();
if (error) {
throw new Error(`Failed to fetch posts: ${error}`);
}
return (posts || []).filter(post =>
post.tags.includes(tag)
);
}
// Update and republish a post
async updatePost(postId: string, updates: Partial<BlogDraft>): Promise<BlogPost> {
const updateData: any = {};
if (updates.title) updateData.title = updates.title;
if (updates.content) updateData.content = updates.content;
if (updates.excerpt) updateData.excerpt = updates.excerpt;
if (updates.tags) updateData.tags = updates.tags;
if (updates.seoData) {
Object.assign(updateData, updates.seoData);
}
const { data: post, error } = await this.sdk.updateBlogPost(postId, updateData);
if (error) {
throw new Error(`Failed to update post: ${error}`);
}
// Log the update
await this.logPostAction("updated", postId, {
changes: Object.keys(updateData),
});
return post!;
}
// Archive a post (soft delete)
async archivePost(postId: string): Promise<void> {
const { error } = await this.sdk.updateBlogPost(postId, {
status: "archived",
});
if (error) {
throw new Error(`Failed to archive post: ${error}`);
}
await this.logPostAction("archived", postId);
}
// Permanently delete a post
async deletePost(postId: string): Promise<void> {
const { error } = await this.sdk.deleteBlogPost(postId);
if (error) {
throw new Error(`Failed to delete post: ${error}`);
}
await this.logPostAction("deleted", postId);
}
// Private helper methods
private extractExcerpt(content: string, maxLength = 200): string {
// Strip HTML tags and get plain text
const text = content.replace(/<[^>]*>/g, '').trim();
if (text.length <= maxLength) {
return text;
}
return text.substring(0, maxLength).trim() + '...';
}
private extractKeywords(content: string, tags?: string[]): string[] {
const keywords = tags || [];
// Simple keyword extraction from content
const text = content.replace(/<[^>]*>/g, '').toLowerCase();
const words = text.split(/s+/);
// Get frequent words (this is very basic - you might want to use a proper NLP library)
const wordCount: Record<string, number> = {};
words.forEach(word => {
if (word.length > 3 && !this.isStopWord(word)) {
wordCount[word] = (wordCount[word] || 0) + 1;
}
});
const frequentWords = Object.entries(wordCount)
.sort(([, a], [, b]) => b - a)
.slice(0, 3)
.map(([word]) => word);
return [...keywords, ...frequentWords];
}
private isStopWord(word: string): boolean {
const stopWords = ['the', 'and', 'or', 'but', 'in', 'on', 'at', 'to', 'for', 'of', 'with', 'by'];
return stopWords.includes(word);
}
private async logPostAction(action: string, postId: string, metadata?: any): Promise<void> {
try {
await this.sdk.createLog({
level: "info",
message: `Blog post ${action}`,
service: "blog-manager",
metadata: {
action,
postId,
timestamp: new Date().toISOString(),
...metadata,
},
});
} catch (error) {
// Don't fail the main operation if logging fails
console.warn("Failed to log post action:", error);
}
}
}
export default BlogManager;
Usage Example
// Using the BlogManager
import BlogManager from './blog-manager';
const blogManager = new BlogManager(process.env.SIMPLYSTACK_API_KEY!);
async function createAndPublishPost() {
try {
// Create a draft first
const draft = await blogManager.createDraft({
title: "Building Scalable APIs with SimplyStack",
content: `
<h1>Introduction</h1>
<p>Building scalable APIs has never been easier with SimplyStack...</p>
<h2>Key Features</h2>
<ul>
<li>Automatic scaling</li>
<li>Built-in authentication</li>
<li>Real-time logging</li>
</ul>
`,
tags: ["api", "development", "tutorial"],
seoData: {
meta_title: "Complete Guide to Building APIs | SimplyStack",
meta_description: "Learn how to build scalable, secure APIs using SimplyStack's powerful platform",
meta_keywords: ["API development", "backend", "scalability", "SimplyStack"],
}
});
console.log("Draft created:", draft.id);
// Publish the post
const published = await blogManager.publishPost({
title: draft.title,
content: draft.content,
tags: draft.tags,
seoData: {
canonical_url: `https://yourblog.com/posts/${draft.id}`,
}
});
console.log("Post published:", published.id);
// Get recent posts
const { posts, hasMore } = await blogManager.getPublishedPosts(1, 5);
console.log(`Found ${posts.length} recent posts, hasMore: ${hasMore}`);
} catch (error) {
console.error("Blog operation failed:", error);
}
}
createAndPublishPost();
Advanced File Upload System
A comprehensive file upload system with validation, progress tracking, and metadata management.
File Upload Manager
// file-upload-manager.ts
import { SimplyStackSDK } from "@simplystack-org/sdk";
import type { StorageAsset } from "@simplystack-org/sdk";
interface UploadConfig {
maxSize?: number; // in bytes
allowedTypes?: string[];
generateThumbnails?: boolean;
addWatermark?: boolean;
}
interface UploadProgress {
file: File;
progress: number;
status: 'pending' | 'uploading' | 'processing' | 'completed' | 'error';
error?: string;
asset?: StorageAsset;
}
class FileUploadManager {
private sdk: SimplyStackSDK;
private defaultConfig: UploadConfig = {
maxSize: 10 * 1024 * 1024, // 10MB
allowedTypes: ['image/jpeg', 'image/png', 'image/gif', 'image/webp'],
generateThumbnails: true,
addWatermark: false,
};
constructor(apiKey: string, config?: Partial<UploadConfig>) {
this.sdk = new SimplyStackSDK(apiKey);
this.defaultConfig = { ...this.defaultConfig, ...config };
}
// Validate file before upload
validateFile(file: File, config?: UploadConfig): { valid: boolean; error?: string } {
const finalConfig = { ...this.defaultConfig, ...config };
// Check file size
if (finalConfig.maxSize && file.size > finalConfig.maxSize) {
return {
valid: false,
error: `File size (${this.formatFileSize(file.size)}) exceeds maximum allowed size (${this.formatFileSize(finalConfig.maxSize)})`,
};
}
// Check file type
if (finalConfig.allowedTypes && !finalConfig.allowedTypes.includes(file.type)) {
return {
valid: false,
error: `File type ${file.type} is not allowed. Allowed types: ${finalConfig.allowedTypes.join(', ')}`,
};
}
return { valid: true };
}
// Upload single file with metadata
async uploadFile(
file: File,
label?: string,
metadata?: Record<string, any>
): Promise<StorageAsset> {
// Validate file
const validation = this.validateFile(file);
if (!validation.valid) {
throw new Error(validation.error);
}
// Prepare metadata
const enhancedMetadata = {
originalName: file.name,
fileSize: file.size,
mimeType: file.type,
uploadedAt: new Date().toISOString(),
uploadedViaAPI: true,
category: this.categorizeFile(file),
...metadata,
};
// Upload file
const { data: asset, error } = await this.sdk.uploadFile(
file,
label || file.name
);
if (error) {
throw new Error(`Upload failed: ${error}`);
}
// Update with enhanced metadata
try {
const { data: updatedAsset } = await this.sdk.updateStorageAsset(
asset!.id,
{
metadata: enhancedMetadata,
}
);
const finalAsset = updatedAsset || asset!;
// Log upload
await this.logUpload(finalAsset, 'success');
return finalAsset;
} catch (updateError) {
// If metadata update fails, return original asset
console.warn("Failed to update metadata:", updateError);
await this.logUpload(asset!, 'success', { metadataUpdateFailed: true });
return asset!;
}
}
// Upload multiple files with progress tracking
async uploadMultipleFiles(
files: File[],
onProgress?: (progress: UploadProgress[]) => void
): Promise<UploadProgress[]> {
const uploadProgresses: UploadProgress[] = files.map(file => ({
file,
progress: 0,
status: 'pending' as const,
}));
// Report initial progress
onProgress?.(uploadProgresses);
// Upload files sequentially (you could make this parallel)
for (let i = 0; i < files.length; i++) {
const file = files[i];
const progress = uploadProgresses[i];
try {
// Update status to uploading
progress.status = 'uploading';
progress.progress = 0;
onProgress?.(uploadProgresses);
// Validate file
const validation = this.validateFile(file);
if (!validation.valid) {
progress.status = 'error';
progress.error = validation.error;
onProgress?.(uploadProgresses);
continue;
}
// Upload file
progress.progress = 50; // Simulate progress
onProgress?.(uploadProgresses);
const asset = await this.uploadFile(file);
// Complete
progress.status = 'completed';
progress.progress = 100;
progress.asset = asset;
onProgress?.(uploadProgresses);
} catch (error) {
progress.status = 'error';
progress.error = error instanceof Error ? error.message : 'Upload failed';
onProgress?.(uploadProgresses);
}
}
return uploadProgresses;
}
// Get uploaded files by category
async getFilesByCategory(category: string): Promise<StorageAsset[]> {
const { data: assets, error } = await this.sdk.getStorageAssets();
if (error) {
throw new Error(`Failed to fetch assets: ${error}`);
}
return (assets || []).filter(asset =>
asset.metadata.category === category
);
}
// Get user's recent uploads
async getRecentUploads(limit = 20): Promise<StorageAsset[]> {
const { data: assets, error } = await this.sdk.getStorageAssets();
if (error) {
throw new Error(`Failed to fetch assets: ${error}`);
}
return (assets || [])
.filter(asset => asset.metadata.uploadedViaAPI)
.sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime())
.slice(0, limit);
}
// Delete file with cleanup
async deleteFile(assetId: string): Promise<void> {
const { error } = await this.sdk.deleteStorageAsset(assetId);
if (error) {
throw new Error(`Failed to delete file: ${error}`);
}
// Log deletion
await this.sdk.createLog({
level: "info",
message: "File deleted",
service: "file-manager",
metadata: {
assetId,
deletedAt: new Date().toISOString(),
},
});
}
// Private helper methods
private categorizeFile(file: File): string {
if (file.type.startsWith('image/')) return 'images';
if (file.type.startsWith('video/')) return 'videos';
if (file.type.startsWith('audio/')) return 'audio';
if (file.type.includes('pdf')) return 'documents';
if (file.type.includes('text/')) return 'documents';
return 'other';
}
private formatFileSize(bytes: number): string {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}
private async logUpload(asset: StorageAsset, status: string, metadata?: any): Promise<void> {
try {
await this.sdk.createLog({
level: status === 'success' ? 'info' : 'error',
message: `File upload ${status}`,
service: "file-manager",
metadata: {
assetId: asset.id,
fileName: asset.metadata.originalName || asset.label,
fileSize: asset.size,
mimeType: asset.mime_type,
category: asset.metadata.category,
...metadata,
},
});
} catch (error) {
console.warn("Failed to log upload:", error);
}
}
}
export default FileUploadManager;
React Component Example
// FileUploadComponent.tsx
import React, { useState, useCallback } from 'react';
import FileUploadManager from './file-upload-manager';
interface UploadProgress {
file: File;
progress: number;
status: 'pending' | 'uploading' | 'processing' | 'completed' | 'error';
error?: string;
asset?: any;
}
const FileUploadComponent: React.FC = () => {
const [dragActive, setDragActive] = useState(false);
const [uploads, setUploads] = useState<UploadProgress[]>([]);
const [fileManager] = useState(() =>
new FileUploadManager(process.env.NEXT_PUBLIC_SIMPLYSTACK_API_KEY!)
);
const handleFiles = useCallback(async (files: FileList) => {
const fileArray = Array.from(files);
await fileManager.uploadMultipleFiles(
fileArray,
(progress) => setUploads([...progress])
);
}, [fileManager]);
const handleDrag = useCallback((e: React.DragEvent) => {
e.preventDefault();
e.stopPropagation();
}, []);
const handleDragIn = useCallback((e: React.DragEvent) => {
e.preventDefault();
e.stopPropagation();
if (e.dataTransfer.items && e.dataTransfer.items.length > 0) {
setDragActive(true);
}
}, []);
const handleDragOut = useCallback((e: React.DragEvent) => {
e.preventDefault();
e.stopPropagation();
setDragActive(false);
}, []);
const handleDrop = useCallback((e: React.DragEvent) => {
e.preventDefault();
e.stopPropagation();
setDragActive(false);
if (e.dataTransfer.files && e.dataTransfer.files.length > 0) {
handleFiles(e.dataTransfer.files);
}
}, [handleFiles]);
const handleFileInput = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
if (e.target.files && e.target.files.length > 0) {
handleFiles(e.target.files);
}
}, [handleFiles]);
return (
<div className="max-w-2xl mx-auto p-6">
<div
className={`border-2 border-dashed rounded-lg p-8 text-center transition-colors ${
dragActive
? 'border-blue-500 bg-blue-50'
: 'border-gray-300 hover:border-gray-400'
}`}
onDragEnter={handleDragIn}
onDragLeave={handleDragOut}
onDragOver={handleDrag}
onDrop={handleDrop}
>
<input
type="file"
multiple
accept="image/*"
onChange={handleFileInput}
className="hidden"
id="file-input"
/>
<label
htmlFor="file-input"
className="cursor-pointer flex flex-col items-center"
>
<svg className="w-12 h-12 text-gray-400 mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12" />
</svg>
<span className="text-lg font-medium text-gray-700 mb-2">
Drop files here or click to browse
</span>
<span className="text-sm text-gray-500">
Supports: JPEG, PNG, GIF, WebP (max 10MB)
</span>
</label>
</div>
{uploads.length > 0 && (
<div className="mt-6 space-y-3">
<h3 className="text-lg font-semibold">Upload Progress</h3>
{uploads.map((upload, index) => (
<div key={index} className="border rounded-lg p-4">
<div className="flex items-center justify-between mb-2">
<span className="font-medium">{upload.file.name}</span>
<span className={`text-sm px-2 py-1 rounded ${
upload.status === 'completed' ? 'bg-green-100 text-green-800' :
upload.status === 'error' ? 'bg-red-100 text-red-800' :
'bg-blue-100 text-blue-800'
}`}>
{upload.status}
</span>
</div>
{upload.status !== 'error' && (
<div className="w-full bg-gray-200 rounded-full h-2">
<div
className="bg-blue-600 h-2 rounded-full transition-all duration-300"
style={{ width: `${upload.progress}%` }}
/>
</div>
)}
{upload.error && (
<div className="text-red-600 text-sm mt-2">{upload.error}</div>
)}
{upload.asset && (
<div className="text-green-600 text-sm mt-2">
✓ Uploaded successfully
{upload.asset.metadata.publicUrl && (
<a
href={upload.asset.metadata.publicUrl}
target="_blank"
rel="noopener noreferrer"
className="ml-2 underline"
>
View
</a>
)}
</div>
)}
</div>
))}
</div>
)}
</div>
);
};
export default FileUploadComponent;
Advanced Logging System
A comprehensive logging system with structured logging, log levels, and analytics.
Advanced Logger Class
// advanced-logger.ts
import { SimplyStackSDK } from "@simplystack-org/sdk";
import type { LogEntry } from "@simplystack-org/sdk";
type LogLevel = 'debug' | 'info' | 'warn' | 'error';
interface LogContext {
userId?: string;
sessionId?: string;
requestId?: string;
userAgent?: string;
ip?: string;
service: string;
}
interface LogMetrics {
executionTime?: number;
memoryUsage?: number;
cpuUsage?: number;
customMetrics?: Record<string, number>;
}
class AdvancedLogger {
private sdk: SimplyStackSDK;
private defaultContext: Partial<LogContext>;
private logQueue: Array<{ level: LogLevel; message: string; context: LogContext; metadata?: any; metrics?: LogMetrics }> = [];
private batchSize = 10;
private batchTimeout = 5000; // 5 seconds
private batchTimer?: NodeJS.Timeout;
constructor(apiKey: string, defaultContext?: Partial<LogContext>) {
this.sdk = new SimplyStackSDK(apiKey);
this.defaultContext = defaultContext || {};
this.startBatchTimer();
}
// Log with automatic context enrichment
async log(
level: LogLevel,
message: string,
context?: Partial<LogContext>,
metadata?: any,
metrics?: LogMetrics
): Promise<void> {
const enrichedContext: LogContext = {
...this.defaultContext,
...context,
service: context?.service || this.defaultContext.service || 'unknown',
};
const enrichedMetadata = {
timestamp: new Date().toISOString(),
environment: process.env.NODE_ENV || 'development',
version: process.env.APP_VERSION || '1.0.0',
...metadata,
context: enrichedContext,
metrics,
};
// Add to batch queue
this.logQueue.push({
level,
message,
context: enrichedContext,
metadata: enrichedMetadata,
metrics,
});
// Process batch if it's full
if (this.logQueue.length >= this.batchSize) {
await this.processBatch();
}
}
// Convenience methods
async debug(message: string, context?: Partial<LogContext>, metadata?: any): Promise<void> {
await this.log('debug', message, context, metadata);
}
async info(message: string, context?: Partial<LogContext>, metadata?: any): Promise<void> {
await this.log('info', message, context, metadata);
}
async warn(message: string, context?: Partial<LogContext>, metadata?: any): Promise<void> {
await this.log('warn', message, context, metadata);
}
async error(message: string, error?: Error, context?: Partial<LogContext>, metadata?: any): Promise<void> {
const errorMetadata = {
...metadata,
error: error ? {
name: error.name,
message: error.message,
stack: error.stack,
} : undefined,
};
await this.log('error', message, context, errorMetadata);
}
// Performance logging
async logPerformance(
operation: string,
duration: number,
context?: Partial<LogContext>,
metrics?: LogMetrics
): Promise<void> {
await this.log('info', `Performance: ${operation}`, context, {
operation,
duration,
type: 'performance',
}, {
executionTime: duration,
...metrics,
});
}
// User action logging
async logUserAction(
action: string,
userId: string,
context?: Partial<LogContext>,
metadata?: any
): Promise<void> {
await this.log('info', `User action: ${action}`, {
...context,
userId,
}, {
action,
type: 'user_action',
...metadata,
});
}
// API request logging
async logApiRequest(
method: string,
endpoint: string,
statusCode: number,
duration: number,
context?: Partial<LogContext>,
metadata?: any
): Promise<void> {
const level: LogLevel = statusCode >= 500 ? 'error' : statusCode >= 400 ? 'warn' : 'info';
await this.log(level, `API ${method} ${endpoint} - ${statusCode}`, context, {
method,
endpoint,
statusCode,
duration,
type: 'api_request',
...metadata,
}, {
executionTime: duration,
});
}
// Search logs with filters
async searchLogs(filters: {
level?: LogLevel;
service?: string;
userId?: string;
startDate?: Date;
endDate?: Date;
keyword?: string;
}): Promise<LogEntry[]> {
const { data: logs, error } = await this.sdk.getLogs({
level: filters.level,
service: filters.service,
});
if (error) {
throw new Error(`Failed to search logs: ${error}`);
}
let filteredLogs = logs || [];
// Additional client-side filtering
if (filters.userId) {
filteredLogs = filteredLogs.filter(log =>
log.metadata?.context?.userId === filters.userId
);
}
if (filters.startDate) {
filteredLogs = filteredLogs.filter(log =>
new Date(log.created_at) >= filters.startDate!
);
}
if (filters.endDate) {
filteredLogs = filteredLogs.filter(log =>
new Date(log.created_at) <= filters.endDate!
);
}
if (filters.keyword) {
const keyword = filters.keyword.toLowerCase();
filteredLogs = filteredLogs.filter(log =>
log.message.toLowerCase().includes(keyword)
);
}
return filteredLogs;
}
// Get log analytics
async getLogAnalytics(timeRange: 'hour' | 'day' | 'week' | 'month'): Promise<{
totalLogs: number;
logsByLevel: Record<LogLevel, number>;
topServices: Array<{ service: string; count: number }>;
errorRate: number;
averageResponseTime?: number;
}> {
const { data: logs, error } = await this.sdk.getLogs();
if (error) {
throw new Error(`Failed to get logs for analytics: ${error}`);
}
const now = new Date();
const timeRanges = {
hour: 60 * 60 * 1000,
day: 24 * 60 * 60 * 1000,
week: 7 * 24 * 60 * 60 * 1000,
month: 30 * 24 * 60 * 60 * 1000,
};
const cutoff = new Date(now.getTime() - timeRanges[timeRange]);
const recentLogs = (logs || []).filter(log =>
new Date(log.created_at) >= cutoff
);
const logsByLevel: Record<LogLevel, number> = {
debug: 0,
info: 0,
warn: 0,
error: 0,
};
const serviceCount: Record<string, number> = {};
let totalResponseTime = 0;
let responseTimeCount = 0;
recentLogs.forEach(log => {
logsByLevel[log.level as LogLevel]++;
if (log.service) {
serviceCount[log.service] = (serviceCount[log.service] || 0) + 1;
}
if (log.metadata?.metrics?.executionTime) {
totalResponseTime += log.metadata.metrics.executionTime;
responseTimeCount++;
}
});
const topServices = Object.entries(serviceCount)
.sort(([, a], [, b]) => b - a)
.slice(0, 10)
.map(([service, count]) => ({ service, count }));
const errorRate = recentLogs.length > 0
? (logsByLevel.error / recentLogs.length) * 100
: 0;
const averageResponseTime = responseTimeCount > 0
? totalResponseTime / responseTimeCount
: undefined;
return {
totalLogs: recentLogs.length,
logsByLevel,
topServices,
errorRate,
averageResponseTime,
};
}
// Batch processing
private async processBatch(): Promise<void> {
if (this.logQueue.length === 0) return;
const batch = [...this.logQueue];
this.logQueue = [];
// Process logs in parallel (but limit concurrency)
const promises = batch.map(logData =>
this.sdk.createLog({
level: logData.level,
message: logData.message,
service: logData.context.service,
metadata: logData.metadata,
}).catch(error => {
// If individual log fails, don't fail the whole batch
console.error('Failed to send log:', error);
})
);
await Promise.allSettled(promises);
}
private startBatchTimer(): void {
this.batchTimer = setInterval(async () => {
if (this.logQueue.length > 0) {
await this.processBatch();
}
}, this.batchTimeout);
}
// Cleanup
async flush(): Promise<void> {
if (this.batchTimer) {
clearInterval(this.batchTimer);
}
await this.processBatch();
}
}
export default AdvancedLogger;
Usage with Express.js Middleware
// express-logging-middleware.ts
import express from 'express';
import AdvancedLogger from './advanced-logger';
const logger = new AdvancedLogger(process.env.SIMPLYSTACK_API_KEY!, {
service: 'api-server',
});
// Request logging middleware
export const requestLogger = (req: express.Request, res: express.Response, next: express.NextFunction) => {
const startTime = Date.now();
// Generate request ID
const requestId = `req_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
req.requestId = requestId;
// Log request start
logger.info('Request started', {
requestId,
service: 'api-server',
ip: req.ip,
userAgent: req.get('User-Agent'),
}, {
method: req.method,
url: req.originalUrl,
headers: req.headers,
query: req.query,
body: req.method !== 'GET' ? req.body : undefined,
});
// Override res.end to log response
const originalEnd = res.end;
res.end = function(chunk: any, encoding?: string) {
const duration = Date.now() - startTime;
// Log request completion
logger.logApiRequest(
req.method,
req.originalUrl,
res.statusCode,
duration,
{
requestId,
service: 'api-server',
ip: req.ip,
userAgent: req.get('User-Agent'),
},
{
responseHeaders: res.getHeaders(),
responseSize: chunk ? chunk.length : 0,
}
);
originalEnd.call(this, chunk, encoding);
};
next();
};
// Error logging middleware
export const errorLogger = (err: Error, req: express.Request, res: express.Response, next: express.NextFunction) => {
logger.error('Request error', err, {
requestId: req.requestId,
service: 'api-server',
ip: req.ip,
userAgent: req.get('User-Agent'),
}, {
method: req.method,
url: req.originalUrl,
statusCode: res.statusCode,
});
next(err);
};
// Performance monitoring
export const performanceMonitor = async (operation: string, fn: () => Promise<any>) => {
const startTime = Date.now();
const startMemory = process.memoryUsage();
try {
const result = await fn();
const duration = Date.now() - startTime;
const endMemory = process.memoryUsage();
await logger.logPerformance(operation, duration, {
service: 'api-server',
}, {
executionTime: duration,
memoryUsage: endMemory.heapUsed - startMemory.heapUsed,
});
return result;
} catch (error) {
const duration = Date.now() - startTime;
await logger.error(`Performance error in ${operation}`, error as Error, {
service: 'api-server',
}, {
operation,
duration,
});
throw error;
}
};
// Usage in Express app
const app = express();
app.use(requestLogger);
app.get('/api/posts', async (req, res) => {
try {
const posts = await performanceMonitor('fetch-posts', async () => {
// Your database query here
return await fetchPostsFromDatabase();
});
res.json(posts);
} catch (error) {
next(error);
}
});
app.use(errorLogger);
// Graceful shutdown
process.on('SIGTERM', async () => {
await logger.flush();
process.exit(0);
});
Integration Patterns
Common patterns for integrating SimplyStack into different types of applications.
🔄 Event-Driven Architecture
Use SimplyStack for event logging and state management in microservices.
📊 Analytics Pipeline
Collect user behavior data and generate insights.
🚀 Content Management
Build headless CMS with blog posts and media management.
🔍 Monitoring & Alerting
Real-time monitoring with intelligent alerting.
Next Steps
Ready to implement these patterns in your project? Here are some next steps:
Choose Your Pattern
Select the example that best matches your use case and copy the relevant code.
Customize for Your Needs
Adapt the examples to fit your specific requirements and business logic.
Test Thoroughly
Test error scenarios, edge cases, and performance under load.
Monitor and Iterate
Use the logging patterns to monitor your implementation and iterate based on real usage.