MoreRSS

site iconThe Practical DeveloperModify

A constructive and inclusive social network for software developers.
Please copy the RSS to your reader, or quickly subscribe to:

Inoreader Feedly Follow Feedbin Local Reader

Rss preview of Blog of The Practical Developer

chatfaster, building ai chat saas, multi-llm chat app development -...

2026-01-03 02:38:08

How I Built ChatFaster: Building AI Chat SaaS, Multi-LLM Chat App Coding

Have you ever felt overwhelmed by the number of AI models coming out every week? In January 2026, the pace of AI feels faster than ever. I wanted a single place to use GPT-4o, Claude 4, and Gemini 2. 5 without jumping between five different tabs. That's why I started ChatFaster, building ai chat saas, multi-llm chat app coding as a solo project to solve my own workflow problems.

Building a production-grade app isn't just about a pretty chat interface. It’s about handling complex state management across different providers while keeping things fast. I’ve spent the last year refining this system. I want to share the technical choices, the late-night bugs. The architecture that makes it all work.

In this guide, I’ll walk you through my journey with ChatFaster, building ai chat saas, multi-llm chat app coding. You’ll see how I used Next. js 16 and NestJS to create a tool that handles everything from RAG to encrypted cloud backups. Whether you’re a dev or a founder, there’s a lot to learn from this build.

My Tech Stack for ChatFaster, Building AI Chat SaaS, Multi-LLM Chat App Coding

When I started ChatFaster, I knew the stack had to be modern and scalable. I chose Next. js 16 with Turbopack for the frontend. It makes the coding loop feel instant. I also used React 19 for its new features. For the backend, I went with NestJS 11 because it provides a solid structure for a growing API.

My core tech stack includes:
• Frontend: Next.
• Backend: NestJS 11 and MongoDB Atlas
• State Management: Zustand (it’s much simpler than Redux for this)
• AI Connection: Vercel AI SDK and @assistant-ui/react
• Infrastructure: Redis for caching and Cloudflare R2 for storage

I followed the Next. js docs closely to improve my server parts. Using Turbopack saved me about 5 hours of waiting for builds every week. It’s those small wins that keep a solo dev going. I also used Tailwind CSS 4 to keep my styling clean and fast.

I chose MongoDB for the database because chat messages are of course nested. Storing messages as embedded documents inside a conversation thread improved my read speed by 40%. Plus, using Redis for distributed rate limiting make sures the app stays up even when traffic spikes.

Why Multi-Provider LLM Abstraction Matters for Devs

One of the biggest hurdles in ChatFaster, building ai chat saas, multi-llm chat app coding was the API mess. Every provider has a different way of handling messages. OpenAI uses one format, while Anthropic and Google use others. I didn't want my frontend to care about these differences.

I built a unified interface to handle over 50 models across 4 providers.
• I created a provider-agnostic wrapper in the backend
• The wrapper maps incoming requests to the specific provider API
• It handles streaming responses using Server-Sent Events (SSE)
• It standardizes tool use, like web search or image generation
• It manages error handling for specific model rate limits

This abstraction is a lifesaver. If a new model drops tomorrow, I can add it to ChatFaster in about 15 minutes. I don't have to rewrite any frontend code. I just update the backend mapping and it’s live. This flexibility is what makes ChatFaster, building ai chat saas, multi-llm chat app coding so powerful for power users.

I also had to deal with varying context windows. Some models handle 4K tokens, while others handle over 1 million. I built an intelligent truncation system. It counts tokens on the fly and uses a sliding window approach. This keeps the conversation going without hitting those annoying "context limit reached" errors.

How to Handle Real-Time Streaming and SSE in Your App

Real-time streaming is what makes an AI app feel "alive. " No one wants to wait 30 seconds for a full paragraph to appear. I used Server-Sent Events (SSE) to stream text from the LLM directly to the user's screen. This was one of the most fun parts of ChatFaster, building ai chat saas, multi-llm chat app coding.

Here is the step-by-step process I used:

  1. The frontend sends a POST request to the NestJS backend
  2. The backend validates the user's subscription and rate limits
  3. I use the Vercel AI SDK to call the LLM provider
  4. The backend pipes the stream back to the client using SSE
  5. The React frontend uses @assistant-ui/react to render the chunks as they arrive

I also integrated tool use events into this stream. For example, if the model needs to search the web, the stream pauses. It sends a "tool call" event. The backend performs the search and sends the results back into the context. Then the stream resumes. It feels smooth to the user.

A common mistake I made early on was not handling connection drops. If the user's Wi-Fi flickered, the stream would break. I added a retry logic and a way to resume the UI state. Now, the app is much more resilient. Most users see a 25% increase in perceived speed because the first word appears almost instantly.

Building a Smart Knowledge Base with RAG and Cloudflare

Most people want their AI to know about their specific business or files. That’s where Retrieval-Augmented Generation (RAG) comes in. For ChatFaster, building ai chat saas, multi-llm chat app coding, I built a dual knowledge base system. You can have organization-wide files or personal ones.

I used a hybrid search approach:
• Semantic search: Using OpenAI embeddings to find meaning
• Keyword search: For finding specific terms or names
• Vector storage: Cloudflare Vectorize for fast, low-latency lookups
• Document chunking: Breaking big PDFs into 500-token pieces
• Confidence scoring: Only showing results that actually match the query

I used Cloudflare R2 for storing the actual files. I don't upload files through my backend because that would be a bottleneck. Instead, I use presigned URLs. The browser uploads directly to R2. This saved me a lot of server costs and made uploads 50% faster for users.

If you want to see how these libraries work together, check out the Vercel AI SDK on GitHub. It’s a great industry resource for anyone building AI apps. I used it to handle the heavy lifting of stream management and model calling.

Common Mistakes to Avoid in Multi-LLM Chat App Coding

I've made plenty of mistakes while working on ChatFaster, building ai chat saas, multi-llm chat app coding. One big one was storing API keys in plain text early in the prototype phase. That's a huge security risk. Now, I use an encrypted vault where the server never even sees your plaintext keys.

Watch out for these common pitfalls:
• Hardcoding model names: Always use a dynamic setup
• Ignoring token costs: Without rate limits, one user can cost you $100 in an hour
• Poor error messaging: "Internal Server Error" doesn't help the user
• Over-complicating the UI: Keep the chat clean and focused
• Forgetting offline mode: Users hate losing work when their net goes out

I solved the offline issue by using an offline-first architecture. I use IndexedDB to store messages locally. Then, I sync the changes to the cloud using a delta sync method. This means you can keep typing even in a tunnel. Once you're back online, everything saves on its own.

Another lesson was about rate limiting. I built a custom throttler in NestJS backed by Redis. It tracks limits based on the user's tier. If you're on a free plan, you get fewer requests per minute than a pro user. This keeps the business sustainable and the speed stable for everyone.

Security and Privacy with End-to-End Encrypted Backups

Privacy is a top priority for me. I know people share sensitive info with AI. For ChatFaster, building ai chat saas, multi-llm chat app coding, I implemented AES-256-GCM encryption for cloud backups. The user controls the encryption key. If I don't have your key, I can't read your chats.

My security setup involves:
• PBKDF2 key derivation for user passwords
• AES-256-GCM for encrypting the message content
• Secure storage of API keys in a dedicated vault
• Firebase Auth for reliable user management
• Regular security audits of the NestJS controllers

I also built a desktop app using Tauri. This allows for a native time on macOS. It includes a deep linking protocol so you can open the app from a browser. Since Tauri uses the system's webview, the app is tiny—only about 10MB. It’s much faster than an Electron app.

Building this project taught me so much about the full stack. From fine-tuning a vector database to handling Stripe subscriptions for seven different tiers. It's been a wild ride. If you're looking for help with React or Next. js, reach out to me. I'm always open to discussing interesting projects.

I'm really proud of how ChatFaster, building ai chat saas, multi-llm chat app coding turned out. It’s a production-grade tool that I use every single day. If you're a dev looking to build something similar, I hope my times help you avoid the hurdles I faced.

signup to see the platform in action. I'm constantly adding new features like the personal memory system. You can use a specific prefix to save things to your long-term AI memory. It makes the chat feel like it actually knows you.

Building in public has been a great way to stay motivated. I've learned that the community is always willing to help if you're honest about your challenges. I'll keep sharing updates as I scale the platform. Let's connect if you want to talk shop about AI or full-stack engineering.

ChatFaster.

Frequently Asked Questions

What is the best tech stack for multi-LLM chat app development?

A modern stack for multi-LLM apps typically includes frameworks like Next.js for the frontend and robust abstraction layers like the Vercel AI SDK or LangChain. This allows developers to switch between providers like OpenAI, Anthropic, and Google Gemini seamlessly while maintaining a consistent user experience across the platform.

How does ChatFaster simplify the process of building an AI chat SaaS?

ChatFaster provides a streamlined architecture that handles the complexities of provider abstraction and real-time streaming out of the box. By using this approach, developers can focus on unique features and user experience rather than reinventing the backend logic required to manage multiple AI models.

Why is Server-Sent Events (SSE) preferred over standard APIs for AI chat?

SSE is essential for delivering real-time, streaming responses, which allows users to see the AI's output as it is being generated. This "typing" effect significantly improves the perceived performance of the app and prevents users from staring at a loading spinner while the full response is processed.

How do RAG and Cloudflare work together to create a smart knowledge base?

Retrieval-Augmented Generation (RAG) allows your AI to query specific documents, while Cloudflare’s edge network provides a high-performance environment for hosting vector databases. This combination ensures that your AI can access and retrieve private data or custom documentation with extremely low latency.

What are the most common mistakes to avoid when building an AI SaaS?

Many developers fail by tightly coupling their code to a single LLM provider, which makes it difficult to pivot when prices or models change. Other common pitfalls include neglecting token cost management and failing to implement robust error handling for when an AI provider experiences downtime.

How can I ensure user privacy with end-to-end encrypted backups?

Implementing end-to-end encryption ensures that chat histories are encrypted on the client side before being stored in the cloud, meaning only the user holds the keys to decrypt them. This level of security is vital for building trust, especially for SaaS applications handling sensitive or proprietary business information.

🔒_Security_Performance_Balance[20260102183741]

2026-01-03 02:37:43

As an engineer who has experienced multiple security incidents, I deeply understand how important the balance between security and performance is in web application development. Recently, I participated in the development of a financial-grade application, which made me rethink the impact of security mechanisms on performance. Today I want to share my experience on how to improve web application performance while ensuring security.

💡 Performance Cost of Security Mechanisms

In modern web applications, security mechanisms bring significant performance overhead:

🔐 Encryption/Decryption Overhead

Operations like TLS/SSL encryption and data encryption consume大量CPU resources.

🔍 Input Validation Overhead

Security checks like XSS protection and SQL injection prevention increase request processing time.

📝 Logging Overhead

Recording security audit logs affects system response speed.

📊 Security Mechanism Performance Test Data

🔬 Performance Comparison of Different Security Levels

I designed a comprehensive security performance test, and the results were thought-provoking:

Basic Security Protection Performance

Framework QPS Latency Increase CPU Overhead Memory Overhead
Hyperlane Framework 334,888.27 +8% +12% +15%
Tokio 340,130.92 +15% +18% +22%
Rocket Framework 298,945.31 +25% +28% +35%
Rust Standard Library 291,218.96 +20% +25% +30%
Gin Framework 242,570.16 +35% +42% +48%
Go Standard Library 234,178.93 +30% +38% +45%
Node Standard Library 139,412.13 +55% +65% +75%

Advanced Security Protection Performance

Framework QPS Latency Increase CPU Overhead Memory Overhead
Hyperlane Framework 287,456.34 +25% +35% +40%
Tokio 298,123.45 +30% +42% +48%
Rocket Framework 245,678.90 +45% +55% +65%
Rust Standard Library 256,789.12 +40% +50% +60%
Gin Framework 198,234.56 +60% +75% +85%
Go Standard Library 189,345.67 +55% +70% +80%
Node Standard Library 98,456.78 +85% +95% +110%

🎯 Core Security Performance Optimization Technologies

🚀 Intelligent Security Detection

The Hyperlane framework adopts intelligent security detection mechanisms, greatly reducing unnecessary performance overhead:

// Intelligent XSS protection
fn intelligent_xss_protection(input: &str) -> String {
    // Machine learning-based XSS detection
    if is_potential_xss(input) {
        // Only perform deep scanning on suspicious content
        deep_xss_scan(input)
    } else {
        // Safe content passes directly
        input.to_string()
    }
}

// Pattern-based security detection
fn pattern_based_security_check(request: &Request) -> SecurityLevel {
    // Analyze request patterns
    let pattern = analyze_request_pattern(request);

    match pattern.risk_level() {
        RiskLevel::Low => SecurityLevel::Basic,
        RiskLevel::Medium => SecurityLevel::Enhanced,
        RiskLevel::High => SecurityLevel::Maximum,
    }
}

🔧 Asynchronous Security Processing

Asynchronous security processing can significantly reduce the impact on request latency:

// Asynchronous security audit
async fn async_security_audit(event: SecurityEvent) {
    // Asynchronously record security events
    tokio::spawn(async move {
        audit_logger.log(event).await;
    });
}

// Asynchronous threat detection
async fn async_threat_detection(request: Request) -> Result<Request> {
    // Parallel threat detection processing
    let threat_check = tokio::spawn(threat_analysis(request.clone()));
    let malware_check = tokio::spawn(malware_scan(request.clone()));

    // Wait for all checks to complete
    let (threat_result, malware_result) = tokio::join!(threat_check, malware_check);

    if threat_result? || malware_result? {
        return Err(SecurityError::ThreatDetected);
    }

    Ok(request)
}

⚡ Caching Security Results

Caching security detection results can avoid repeated calculations:

// Security result caching
struct SecurityCache {
    cache: LruCache<String, SecurityResult>,
    ttl: Duration,
}

impl SecurityCache {
    async fn check_security(&mut self, key: &str) -> SecurityResult {
        // Check cache
        if let Some(result) = self.cache.get(key) {
            if result.is_fresh(self.ttl) {
                return result.clone();
            }
        }

        // Perform security check
        let result = perform_security_check(key).await;
        self.cache.put(key.to_string(), result.clone());

        result
    }
}

💻 Security Implementation Analysis

🐢 Security Performance Issues in Node.js

Node.js has obvious performance problems in security processing:

const express = require('express');
const helmet = require('helmet');
const xss = require('xss');

const app = express();

// Security middleware brings significant performance overhead
app.use(helmet()); // Security header settings
app.use(express.json({ limit: '10mb' })); // Request size limits

app.post('/api/data', (req, res) => {
    // XSS protection has high overhead
    const cleanData = xss(req.body.data); // Synchronous processing, blocks event loop

    // SQL injection protection
    const query = 'SELECT * FROM users WHERE id = ?';
    db.query(query, [cleanData.id], (err, results) => {
        res.json(results);
    });
});

app.listen(60000);

Problem Analysis:

  1. Synchronous Security Processing: Operations like XSS protection block the event loop
  2. Repeated Security Checks: Lack of effective caching mechanisms
  3. High Memory Usage: Security libraries typically consume more memory
  4. Lack of Intelligent Detection: Same level of security checks for all requests

🐹 Security Performance Features of Go

Go has a relatively balanced approach to security processing:

package main

import (
    "crypto/tls"
    "net/http"
    "time"
)

func securityMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        // Concurrent security checks
        go func() {
            // Asynchronous security audit
            auditRequest(r)
        }()

        // Quick security checks
        if !quickSecurityCheck(r) {
            http.Error(w, "Security check failed", 403)
            return
        }

        next.ServeHTTP(w, r)
    })
}

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("/", handler)

    // TLS configuration optimization
    srv := &http.Server{
        Addr: ":60000",
        Handler: securityMiddleware(mux),
        TLSConfig: &tls.Config{
            MinVersion: tls.VersionTLS12,
            CurvePreferences: []tls.CurveID{tls.CurveP521, tls.CurveP384, tls.CurveP256},
        },
        ReadTimeout: 5 * time.Second,
        WriteTimeout: 10 * time.Second,
    }

    srv.ListenAndServeTLS("cert.pem", "key.pem")
}

Advantage Analysis:

  1. Goroutine Concurrency: Can process security checks in parallel
  2. Comprehensive Standard Library: Packages like crypto/tls provide good security support
  3. Memory Management: Relatively good memory usage efficiency

Disadvantage Analysis:

  1. GC Impact: Temporary objects generated by security processing affect GC
  2. Lack of Intelligent Detection: Security policies are relatively fixed

🚀 Security Performance Advantages of Rust

Rust has natural advantages in security performance:

use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::RwLock;

// Zero-cost security abstractions
struct SecurityContext {
    // Compile-time security checks
    permissions: Vec<Permission>,
    // Runtime security state
    security_level: SecurityLevel,
}

// Asynchronous security processing
async fn secure_request_handler(
    request: Request,
    security_ctx: Arc<RwLock<SecurityContext>>
) -> Result<Response> {
    // Parallel security checks
    let security_check = async {
        let ctx = security_ctx.read().await;
        ctx.validate_request(&request)
    };

    let threat_detection = async {
        detect_threats(&request).await
    };

    // Concurrent execution of security checks
    let (security_result, threat_result) = tokio::join!(security_check, threat_detection);

    if !security_result? || threat_result? {
        return Err(SecurityError::ValidationFailed);
    }

    // Security processing complete, execute business logic
    process_request(request).await
}

// Memory-safe data processing
fn safe_data_processing(data: &[u8]) -> Result<ProcessedData> {
    // Ownership system guarantees memory safety
    let mut buffer = Vec::with_capacity(data.len());
    buffer.extend_from_slice(data);

    // Zero-copy data processing
    let processed = process_without_copy(&buffer)?;

    Ok(processed)
}

Advantage Analysis:

  1. Zero-Cost Abstractions: Compile-time security checks, no runtime overhead
  2. Memory Safety: Ownership system avoids memory-related security issues
  3. Asynchronous Processing: async/await provides efficient asynchronous security processing capabilities
  4. Precise Control: Can precisely control when security policies are executed

🎯 Production Environment Security Performance Optimization Practice

🏪 Financial System Security Optimization

In our financial system, I implemented the following security performance optimization measures:

Layered Security Strategy

// Layered security protection
struct LayeredSecurity {
    // Layer 1: Quick checks
    quick_checks: Vec<QuickSecurityCheck>,
    // Layer 2: Deep checks
    deep_checks: Vec<DeepSecurityCheck>,
    // Layer 3: Real-time monitoring
    realtime_monitor: RealtimeSecurityMonitor,
}

impl LayeredSecurity {
    async fn check_request(&self, request: &Request) -> SecurityResult {
        // Layer 1: Quick checks (90% of requests pass at this layer)
        for check in &self.quick_checks {
            if !check.quick_validate(request)? {
                return SecurityResult::Rejected;
            }
        }

        // Layer 2: Deep checks (9% of requests need this layer)
        if self.needs_deep_check(request) {
            for check in &self.deep_checks {
                if !check.deep_validate(request).await? {
                    return SecurityResult::Rejected;
                }
            }
        }

        // Layer 3: Real-time monitoring (1% of high-risk requests)
        if self.is_high_risk(request) {
            self.realtime_monitor.track(request).await?;
        }

        SecurityResult::Accepted
    }
}

Intelligent Caching Strategy

// Intelligent security caching
struct IntelligentSecurityCache {
    // Risk-level based caching strategy
    low_risk_cache: LruCache<String, SecurityResult>,
    medium_risk_cache: LruCache<String, SecurityResult>,
    high_risk_cache: LruCache<String, SecurityResult>,
}

impl IntelligentSecurityCache {
    async fn get_security_result(&mut self, key: &str, risk_level: RiskLevel) -> SecurityResult {
        match risk_level {
            RiskLevel::Low => {
                // Low risk: Long-term caching
                self.low_risk_cache.get_or_insert_with(key, || {
                    perform_security_check(key)
                })
            }
            RiskLevel::Medium => {
                // Medium risk: Medium-term caching
                self.medium_risk_cache.get_or_insert_with(key, || {
                    perform_security_check(key)
                })
            }
            RiskLevel::High => {
                // High risk: Short-term caching or no caching
                perform_security_check(key)
            }
        }
    }
}

💳 Payment System Security Optimization

Payment systems have the highest security requirements but also need to ensure performance:

Hardware-Accelerated Encryption

// Hardware-accelerated encryption
fn hardware_accelerated_encrypt(data: &[u8], key: &[u8]) -> Result<Vec<u8>> {
    // Use AES-NI instruction set for accelerated encryption
    let cipher = Aes256Cbc::new_from_slices(key, iv)?;
    let encrypted = cipher.encrypt_vec(data);
    Ok(encrypted)
}

// TLS hardware acceleration
fn configure_hardware_tls() -> Result<TlsConfig> {
    let mut config = TlsConfig::new();

    // Enable hardware acceleration
    config.enable_hardware_acceleration()?;

    // Optimize cipher suites
    config.set_ciphers(&[
        TlsCipher::TLS13_AES_256_GCM_SHA384,
        TlsCipher::TLS13_CHACHA20_POLY1305_SHA256,
    ])?;

    Ok(config)
}

Asynchronous Audit Logging

// Asynchronous security audit
struct AsyncAuditLogger {
    log_queue: mpsc::UnboundedChannel<AuditEvent>,
    writer_task: JoinHandle<()>,
}

impl AsyncAuditLogger {
    async fn log_event(&self, event: AuditEvent) {
        // Asynchronously send audit events
        let _ = self.log_queue.send(event);
    }

    async fn start_writer(&self) {
        while let Some(event) = self.log_queue.recv().await {
            // Batch write audit logs
            self.write_audit_log(event).await;
        }
    }
}

🔮 Future Security Performance Development Trends

🚀 AI-Driven Security Optimization

Future security performance optimization will rely more on AI technology:

Machine Learning Threat Detection

// Machine learning-based threat detection
struct MLThreatDetector {
    model: Arc<Mutex<ThreatDetectionModel>>,
    feature_extractor: FeatureExtractor,
}

impl MLThreatDetector {
    async fn detect_threats(&self, request: &Request) -> ThreatLevel {
        // Extract features
        let features = self.feature_extractor.extract_features(request);

        // Use machine learning model to predict threat level
        let model = self.model.lock().await;
        let threat_level = model.predict(&features).await;

        threat_level
    }
}

Adaptive Security Policies

// Adaptive security policy
struct AdaptiveSecurityPolicy {
    policy_engine: PolicyEngine,
    performance_monitor: PerformanceMonitor,
}

impl AdaptiveSecurityPolicy {
    async fn adjust_security_level(&self) {
        // Monitor system performance
        let performance = self.performance_monitor.get_metrics().await;

        // Adjust security level based on performance
        if performance.cpu_usage > 80.0 {
            self.policy_engine.reduce_security_level().await;
        } else if performance.cpu_usage < 50.0 {
            self.policy_engine.increase_security_level().await;
        }
    }
}

🎯 Summary

Through this practical security performance optimization, I have deeply realized that balancing security and performance is an art. The Hyperlane framework excels in intelligent security detection and asynchronous processing, able to minimize performance overhead while ensuring security. Rust's ownership system and zero-cost abstractions provide a solid foundation for security performance optimization.

Security performance optimization requires finding the best balance between protecting system security and ensuring user experience. Choosing the right framework and optimization strategy has a decisive impact on the overall system performance. I hope my practical experience can help everyone achieve better results in security performance optimization.

GitHub Homepage: https://github.com/hyperlane-dev/hyperlane

Most AI No-Code Tools Build Demos. I Built FoundersKit to Build Startups

2026-01-03 02:32:46

This is a submission for the DEV's Worldwide Show and Tell Challenge Presented by Mux

What I Built

FoundersKit is a production-ready SaaS toolkit that helps founders go from idea to a real, scalable startup in days instead of months.

In a world full of AI no-code tools that help you build fast but lock you into non-exportable, non-scalable platforms, FoundersKit focuses on something more important: ownership.

It provides founders with real, customizable code, modern SaaS architecture, and growth-ready tooling so what they launch on day one can scale into a real business.

Website: https://founderskit.in

My Pitch Video

Demo

Live Website: https://founderskit.in
How to Explore:

  • Visit the site to understand the value proposition and feature set
  • Documentation and setup guides explain how founders can quickly launch and customize their SaaS

Hehehe, a free starter template for everyone: https://github.com/Ritesh2351235/FoundersKit-starterplan

DEV judges, drop a comment on this post to get a promo code. I’m happy to offer FoundersKit Pro for free if you’re building something exciting.

The Story Behind It

Over the last year, I saw many founders, including myself, use AI no-code tools like Lovable and Bolt.new to validate ideas quickly. While these tools are impressive, they usually stop at beautiful dashboards.

The problem appears when you want to:

  • Export your code
  • Customize core logic
  • Scale infrastructure
  • Build a real startup roadmap

At that point, you realize you don’t truly own the product.

I built FoundersKit to bridge that gap, combining the speed of modern tooling with the control and scalability of real production code. The goal is simple: help founders move fast without sacrificing their future.

Technical Highlights

FoundersKit is built with real-world scalability in mind and uses modern, startup-proven technologies:

  • Next.js-based SaaS architecture
  • Pre-configured authentication and billing flows
  • Modular, extensible codebase for rapid customization
  • AI integration patterns that work in real production apps
  • Designed for easy deployment and long-term maintenance

Unlike no-code platforms, everything in FoundersKit is:

  • Fully exportable
  • Customizable
  • Deployable anywhere
  • Scalable without platform limitations

This makes it suitable not just for MVPs, but for long-term startup growth.

❗ By submitting this project, I confirm that my video adheres to Mux's terms of service: https://www.mux.com/terms

Thanks for checking out FoundersKit and happy building!

1Password Breaks Syntax Highlighting On The Internet

2026-01-03 02:30:00

READ FIRST: It has been patched, and the latest official update from the 1Password team

Starting sometime in early December of 2025, a change was rolled out to the 1Password browser extension that began injecting prism.js onto every site and targeting every <code> block it encountered.

The original discovery was made by @saltyaom and shared on Twitter to their disbelief.

// Detect dark theme var iframe = document.getElementById('tweet-2005701290870087817-272'); if (document.body.className.includes('dark-theme')) { iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=2005701290870087817&theme=dark" }

The moment it hit my radar was when @yyx990803 retweeted it, amplifying it to a much larger audience.
// Detect dark theme var iframe = document.getElementById('tweet-2005904473332564339-401'); if (document.body.className.includes('dark-theme')) { iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=2005904473332564339&theme=dark" }

1Password has several forum posts on this topic showing that this bug was introduced to all production users.

The biggest question I had was: why are they even injecting Prism.js in the first place?

It turns out this is for a new Labs Snippets feature that allows you to paste snippets from the browser extension. One of the most interesting parts is that it has nothing to do with programming in general and is instead meant for rich text formatting.

Prism.js was targeting every <code> block on any site, ultimately stripping the correct syntax highlighting theme.

The snippets feature itself is actually pretty neat. In this example, it replaces what I type — "sig" — with console.log(67). It’s not clear to me why this requires highlighting <code> blocks on websites, though, since this feature behaves more like TextExpander or macOS text replacements.

// Detect dark theme var iframe = document.getElementById('tweet-2007089632660639822-286'); if (document.body.className.includes('dark-theme')) { iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=2007089632660639822&theme=dark" }

It sucks to have a bug roll out during the holidays when people are on vacation, so I understand the delay in the fix. I’m very excited to read their postmortem to learn how this made it into production and what they plan to do to prevent this from happening again.

I hope you enjoyed this read, and if you’re interested in deep dives like this, please consider following me.

How I built Upple: A modern uptime monitor with Go and React

2026-01-03 02:26:16

I built Upple because I wanted to understand monitoring from the ground up—and because I had specific opinions about how health checks, incidents, and workflows should fit together. It's an uptime monitor and incident management platform that runs health checks, triggers automated workflows, and lets teams respond to incidents in real-time.

The stack is quite straightforward: Go backend, React/TypeScript frontend, PostgreSQL for persistence, and Redis for the event bus.

What Upple Does

Quick overview of the main features:

  • Health checks: HTTP, TCP, DNS, SSL certificate expiry, and ICMP (ping). Configurable consecutive failure thresholds to avoid false positives.

  • Incident management: Incidents can be created manually or auto-detected when monitors fail. There's a - again, pretty standard - status flow (Investigating → Identified → Monitoring → Resolved) with timeline updates and impact levels.

  • Workflow builder: A visual drag-and-drop builder similar to n8n (far from being on par with n8n's extensive set of features though). You compose blocks to create complex monitoring scenarios—login to your app, add an item to cart, checkout, verify the confirmation page. These workflows run as monitors, catching issues that simple HTTP pings would miss.

  • Alert rules: Configurable rules for what happens when things go wrong—create incidents, notify Slack or Discord, page someone via PagerDuty, escalate after a timeout. Similar to PagerDuty's escalation policies.

  • Real-time collaboration: When an incident is active, team members see live updates, presence indicators, and can add timeline comments. All powered by Server-Sent Events.

  • Public status pages: Customer-facing dashboards showing uptime history and current status.

  • Integrations: Slack and Discord for notifications and incident channel sync, PagerDuty for on-call escalation.

  • Maintenance windows: Schedule downtime windows so monitors don't trigger false incidents during planned deployments.

Nothing revolutionary on the feature list. The interesting parts are under the hood.

Thinking about scale before you need it

One advantage of building an indie project with no deadline: you can afford to think about architecture upfront. I spent a fair amount of time on scalability and performance decisions early on, because these are hard problems to tackle later, and it's been really fun, so far, to challenge myself with this. Two patterns ended up shaping most of the backend.

Event delivery

When I started thinking about running multiple pods in Kubernetes, I realized I had conflicting requirements for events.

Take a health check result coming in. On one hand, exactly one pod should process it: store the result, update the monitor status, maybe create an incident. If every pod processed it, I'd get duplicate writes and race conditions.

On the other hand, that same result needs to reach every pod so they can push updates to their connected SSE clients. If only one pod received it, users connected to other pods wouldn't see real-time updates.

Same event, two different delivery patterns.

I'm using Watermill for the event bus with Redis Streams as the backend. Redis Streams has this concept of consumer groups; consumers in the same group split messages between them, while different groups each receive all messages.

So I created two subscribers:

// Work-queue subscriber with shared consumer group
// Only ONE consumer in the group receives each message
sub, err := redisstream.NewSubscriber(
    redisstream.SubscriberConfig{
        Client:        client,
        ConsumerGroup: "upple-workers",
        Consumer:      consumerID,
    }
)

// Broadcast subscriber with per-pod consumer group
// EVERY pod receives EVERY message
broadcastSub, err := redisstream.NewSubscriber(
    redisstream.SubscriberConfig{
        Client:        client,
        ConsumerGroup: fmt.Sprintf("upple-broadcast-%s", consumerID), // Unique per pod
        Consumer:      consumerID,
    }
)

The consumerID is a UUID generated at startup. For work-queue handlers, all pods join upple-workers, so Redis distributes messages across them. For broadcast handlers, each pod creates its own group (upple-broadcast-abc123), so each pod receives every message.

The event bus then exposes two subscription methods:

// Work-queue: one pod processes
eventBus.Subscribe("check.result", resultHandler.Handle)

// Broadcast: all pods receive (for SSE fan-out)
eventBus.SubscribeBroadcast("check.result", sseBroadcaster.OnCheckResult)

Same topic, different delivery guarantees. The ResultHandler stores the check in the database (only runs once), while the SSE broadcaster pushes updates to all connected clients (runs on every pod).

This pattern also degrades gracefully for local development—when using the in-memory event bus instead of Redis, both subscribers get the same channel, which works fine with a single instance.

Managing complex UI state in the workflow builder

The workflow builder was the most challenging part of the frontend. It's a drag-and-drop interface using React Flow where users create nodes (HTTP requests, conditions, delays, loops), connect them, and watch execution status update in real-time.

I went through the usual progression: started with React's built-in state, moved to Context when prop drilling got painful, tried Zustand when Context re-renders became a problem. Each time I'd add a feature—undo/redo, real-time execution visualization, dirty state tracking—and the state interactions would get tangled.

Eventually I tried XState, and it stuck. Here's what I needed:

  • Undo/redo that works across node positions, connections, and data changes
  • Real-time execution visualization via SSE events
  • Dirty state tracking for "You have unsaved changes" prompts
  • Sidebar panel that stays in sync with canvas selection
  • Navigation blocking when dirty

What finally worked: XState with multiple child machines.

Instead of one massive state machine, I split concerns into five specialized machines:

invoke: [
  { id: "canvas", src: "canvasMachine" },       // React Flow state
  { id: "execution", src: "executionMachine" }, // Workflow execution lifecycle
  { id: "sse", src: "sseConnectionMachine" },   // SSE connection management
  { id: "sidebar", src: "sidebarMachine" },     // Panel state
  { id: "navigation", src: "navigationMachine" }, // Dirty tracking + route guards
],

Each machine handles its own complexity. The parent machine just coordinates—when a workflow loads, it tells the canvas machine to initialize and the SSE machine to connect. When an SSE event arrives, it forwards to the execution machine. When the user edits something, the navigation machine tracks dirty state.

Machines communicate using XState's sendTo:

markNavigationDirty: sendTo("navigation", { type: "MARK_DIRTY" }),

This means the parent doesn't need to know how navigation tracking works, just that it needs to be notified.

One subtle problem I ran into: when you drag a node in React Flow, it fires hundreds of position change events. If each one pushed to the undo stack, pressing Ctrl+Z would step back one pixel at a time.

The fix was to batch drag operations into a single history entry:

const onNodesChange: OnNodesChange = useCallback((changes) => {
  const isDragStart = changes.some(c => c.type === "position" && c.dragging === true);
  const isDragEnd = changes.some(c => c.type === "position" && c.dragging === false);

  if (isDragStart && !isDraggingRef.current) {
    // Starting drag - snapshot current state
    isDraggingRef.current = true;
    preDragStateRef.current = history.present;
  }

  if (isDragEnd && isDraggingRef.current) {
    // Drag finished - push the pre-drag snapshot as ONE history entry
    isDraggingRef.current = false;
    setHistory(prev => ({
      past: [...prev.past, preDragStateRef.current!].slice(-MAX_HISTORY_SIZE),
      present: { ...prev.present, nodes: newNodes },
      future: [],
    }));
  }
}, [history.present]);

React Flow tells us when dragging starts (dragging: true) and ends (dragging: false). We capture the state before the drag starts and only commit to history when the drag ends. Dragging 5 nodes across the canvas becomes 1 undo operation.

Looking Back

Trying to stick to the parts of DDD that felt relevant to me was worth the upfront investment. Separating monitors, incidents, workflows, and alerts into distinct aggregates with clear boundaries made the codebase navigable. When I need to change how incidents work, I know exactly where to look.

XState has a STEEP learning curve, but I feel like it pays off. The first few weeks were slow—thinking in states and events felt unnatural. But once I internalized it, features that would have been messy with useState became straightforward. The dev tools that let you visualize the state machine are genuinely super useful for debugging.

SSE is simpler than WebSockets for server-push. I don't need bidirectional communication, the client only needs to receive events, not send them. SSE handles reconnection automatically, works with standard HTTP middleware, and doesn't require a separate protocol.

Abstracting the event bus paid off. I started with an in-memory event bus for local development, knowing I'd switch to Redis for production. When the time came, the migration took less than an hour because the abstraction was clean—handlers didn't care whether events came from memory or Redis Streams.

Upple is live at upple.io. Most of its features are available for free with a very generous free tier, paid tier isn't even available yet. Don't hesitate to try it, any feedbacks would be very welcome. If you have questions about the architecture or want to discuss the patterns I described, drop a comment, I'm curious how others have solved similar problems!

**Production-Ready Go Docker Containers: Small, Secure, and Efficient Containerization Guide**

2026-01-03 02:21:38

As a best-selling author, I invite you to explore my books on Amazon. Don't forget to follow me on Medium and show your support. Thank you! Your support means the world!

Let’s talk about putting a Go application into a Docker container that’s ready for production. I don’t mean just any container—I mean a small, secure, and efficient one. Over the years, I've learned that a bloated container can slow you down, increase costs, and introduce security risks. So, let me walk you through how I build them.

The goal is simple: package your application so it runs consistently anywhere, using as little space and memory as possible, without cutting corners on security. We'll start with a basic Go web application. Here’s what that might look like.

package main

import (
    "fmt"
    "log"
    "net/http"
    "os"
    "runtime"
    "time"
)

func HealthHandler(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "application/json")
    fmt.Fprintf(w, `{"status":"healthy","timestamp":"%s"}`, time.Now().UTC().Format(time.RFC3339))
}

func MetricsHandler(w http.ResponseWriter, r *http.Request) {
    var m runtime.MemStats
    runtime.ReadMemStats(&m)

    w.Header().Set("Content-Type", "text/plain")
    fmt.Fprintf(w, `# HELP go_memstats_alloc_bytes Number of bytes allocated
go_memstats_alloc_bytes %d
# HELP go_goroutines Number of goroutines
go_goroutines %d
`, m.Alloc, runtime.NumGoroutine())
}

func main() {
    port := os.Getenv("PORT")
    if port == "" {
        port = "8080"
    }

    http.HandleFunc("/health", HealthHandler)
    http.HandleFunc("/metrics", MetricsHandler)
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, "Go Application v1.0.0")
    })

    log.Printf("Server starting on port %s", port)
    log.Fatal(http.ListenAndServe(":"+port, nil))
}

This app has three endpoints: a root handler, a health check, and a simple metrics endpoint. It's a typical starting point. The real work begins with the Dockerfile. This is where many people go wrong by creating a single, bulky stage that includes the compiler, source code, and the final binary all in one image.

Instead, I use a method called a multi-stage build. Think of it like building a car in a factory and then shipping only the finished car, not the entire factory. The first stage is the builder stage, where we compile the Go code.

# Build stage
FROM golang:1.21-alpine AS builder

RUN apk add --no-cache git ca-certificates tzdata
RUN adduser -D -g '' appuser

WORKDIR /build
COPY go.mod go.sum ./
RUN go mod download

COPY . .
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \
    go build -a \
    -ldflags="-w -s -extldflags '-static' \
    -X main.version=$(git describe --tags --always) \
    -X main.buildTime=$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
    -o /app .

Notice a few things. I start with golang:1.21-alpine, which is a smaller base image. I install only what’s needed: git for version info, ca-certificates for SSL, and tzdata for timezone support. I also create a non-root user called appuser right here in the builder stage. This is a security practice I always follow; the application should not run as root.

The build flags are crucial. CGO_ENABLED=0 creates a static binary that doesn’t depend on C libraries from the operating system. The -ldflags="-w -s" strips debug symbols, making the binary smaller. The -X flags inject version and build time directly into the binary, which is helpful for tracking what’s running.

Now, here’s the magic of multi-stage. I can create a second stage that starts from a completely empty base, called scratch.

# Final stage
FROM scratch

COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo
COPY --from=builder /etc/passwd /etc/passwd

WORKDIR /app
COPY --from=builder /app /app/app

USER appuser

HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
    CMD ["/app/app", "health"]

EXPOSE 8080

ENV PORT=8080 \
    TZ=UTC

ENTRYPOINT ["/app/app"]

The scratch image is literally empty. It contains no operating system, no shell, nothing. This is the ultimate minimal footprint. Into this empty container, I copy only what the static binary needs: the SSL certificates, timezone data, the user information file, and the binary itself. That’s it.

The result is an image that’s often just 5 to 10 megabytes. It starts in milliseconds. I once replaced a 1.2 GB development-style image with a 7 MB one like this, and the performance and security improvements were immediate.

But a small image isn’t useful if it’s insecure. Let’s talk about security hardening. I already mentioned the non-root user. Running as appuser means if someone finds a way into the container, they have very limited privileges. I also copy the /etc/passwd file just to define that user; the container won’t even have a shell for them to use.

Another layer is a security scan. I can add a dedicated scanning stage to my Dockerfile using a tool like Trivy.

# Security scan stage
FROM aquasec/trivy:latest AS scanner
COPY --from=builder /app /app
RUN trivy filesystem --severity HIGH,CRITICAL --no-progress /app

This stage scans the compiled binary for known vulnerabilities before it ever gets into the final image. It acts as a gate in the build process. In a team setting, this can be enforced in your continuous integration pipeline.

Now, development and testing are just as important. I use Docker Compose to create a consistent local environment that mirrors the build stage.

services:
  app:
    build:
      context: .
      target: builder
    ports:
      - "8080:8080"
    environment:
      - PORT=8080
      - DEBUG=true
    volumes:
      - .:/build
      - go-mod-cache:/go/pkg/mod
    command: go run main.go

  test:
    build:
      context: .
      target: builder
    environment:
      - GO111MODULE=on
    volumes:
      - .:/build
      - go-mod-cache:/go/pkg/mod
    command: go test -v ./...

This docker-compose.yml file lets me run the application in development with hot-reloading (go run), run my test suite, and even run linters, all within isolated containers that share a cached module directory. It keeps my local machine clean and ensures everyone on the team has the same experience.

Automation is key for consistency. I write a simple shell script to handle the build, test, scan, and push process.

#!/bin/bash
set -e

APP_NAME="go-app"
REGISTRY="registry.example.com"
VERSION=${1:-$(git describe --tags --always)}

echo "Building container ${APP_NAME}:${VERSION}"
docker build \
    --build-arg VERSION=${VERSION} \
    --tag ${APP_NAME}:${VERSION} \
    --tag ${APP_NAME}:latest \
    --target builder \
    -f Dockerfile .

echo "Running security scan"
docker run --rm \
    -v /var/run/docker.sock:/var/run/docker.sock \
    aquasec/trivy:latest \
    image --severity HIGH,CRITICAL ${APP_NAME}:${VERSION}

echo "Running tests"
docker run --rm ${APP_NAME}:${VERSION} go test -v -race ./...

echo "Building production image"
docker build \
    --build-arg VERSION=${VERSION} \
    --tag ${REGISTRY}/${APP_NAME}:${VERSION} \
    --tag ${REGISTRY}/${APP_NAME}:latest \
    -f Dockerfile .

echo "Build completed: ${REGISTRY}/${APP_NAME}:${VERSION}"

This script ensures every production image is tagged with a version, passes a security scan, and passes all tests. It turns a complex series of commands into a single, reliable step.

Let’s not forget about the application’s behavior inside the container. The HEALTHCHECK instruction in the Dockerfile is vital. It tells the container platform (like Docker Swarm or Kubernetes) how to check if my app is alive and ready. My /health endpoint returns a simple JSON status. Without this, your orchestrator won’t know if your app is stuck starting up or has crashed.

Resource limits are another practical consideration. While not in the Dockerfile itself, when you run or deploy the container, you should set limits. A Go application’s garbage collector can be tuned, but it works better if it knows its constraints. If you tell Kubernetes the container needs 100Mi of memory, the Go runtime can manage its heap more efficiently.

Configuration should always come from outside the container. My app reads the PORT from an environment variable. For secrets like API keys, I use the secret management system built into my orchestrator, never baking them into the image or passing them via build arguments.

Monitoring is built right into the sample application with the /metrics endpoint. It exposes Go runtime metrics in a format that Prometheus can scrape. When this container runs in Kubernetes, I can set up a ServiceMonitor to collect these metrics automatically. All application logs go to standard output (log.Printf), where they can be collected by tools like Fluentd or Loki.

Finally, let’s validate that our container works as expected with some integration tests.

package main

import (
    "net/http"
    "testing"
    "time"
)

func TestContainerHealth(t *testing.T) {
    go func() {
        main()
    }()

    time.Sleep(100 * time.Millisecond)

    resp, err := http.Get("http://localhost:8080/health")
    if err != nil {
        t.Fatalf("Health check failed: %v", err)
    }
    defer resp.Body.Close()

    if resp.StatusCode != http.StatusOK {
        t.Errorf("Expected status 200, got %d", resp.StatusCode)
    }
}

These tests can be run inside the container during the build process to ensure the packaged application responds correctly.

To sum it up, building a production-ready container is about intentional choices. Use multi-stage builds to separate the build environment from the runtime environment. Start from scratch or a minimal base like alpine. Always run as a non-root user. Inject version information at build time. Set up health checks and expose metrics. Automate security scanning and testing.

The outcome is a container that is small, fast, and secure. It uses resources predictably and fits perfectly into modern, automated deployment pipelines. It gives you confidence that what runs on your laptop will run the same way in the cloud, with no surprises. This approach has served me well across countless projects, and it’s a foundation you can build upon for any Go service.

📘 Checkout my latest ebook for free on my channel!

Be sure to like, share, comment, and subscribe to the channel!

101 Books

101 Books is an AI-driven publishing company co-founded by author Aarav Joshi. By leveraging advanced AI technology, we keep our publishing costs incredibly low—some books are priced as low as $4—making quality knowledge accessible to everyone.

Check out our book Golang Clean Code available on Amazon.

Stay tuned for updates and exciting news. When shopping for books, search for Aarav Joshi to find more of our titles. Use the provided link to enjoy special discounts!

Our Creations

Be sure to check out our creations:

Investor Central | Investor Central Spanish | Investor Central German | Smart Living | Epochs & Echoes | Puzzling Mysteries | Hindutva | Elite Dev | Java Elite Dev | Golang Elite Dev | Python Elite Dev | JS Elite Dev | JS Schools

We are on Medium

Tech Koala Insights | Epochs & Echoes World | Investor Central Medium | Puzzling Mysteries Medium | Science & Epochs Medium | Modern Hindutva