PureMix Documentation

PureMix is a server-side rendering framework inspired by Remix, that lets you build web applications using HTML syntax while seamlessly mixing JavaScript, TypeScript, and Python in the same file.

⚠️ Alpha Version: PureMix is currently in alpha (v0.1.0-alpha.1). APIs may change before stable release.

Framework Architecture

Behind the scenes, PureMix is powered by Node.js and Express.js. The framework provides a high-level abstraction layer that handles routing, template processing, and Python integration, while Express handles the HTTP server and middleware stack.

🟢 Node.js Runtime

Node.js 22+ provides native TypeScript support and the JavaScript execution environment for server-side code.

⚡ Express.js Server

Express handles HTTP requests, routing, middleware, and serves as the web server foundation.

🎨 PureMix Engine

Custom engine layer that processes .puremix files, handles templates, loaders, actions, and component rendering.

🐍 Python Bridge

Process pool management system that executes Python code in isolated workers for seamless JavaScript/Python integration.

Request Lifecycle

Understanding how PureMix processes requests:

  1. HTTP Request → Express receives the request (GET, POST, etc.)
  2. Route Resolution → PureMix maps URL to .puremix file using file-based routing
  3. Action Execution → If POST, server functions execute with form data
  4. Loader Execution → Data fetching function runs (receives action results if POST)
  5. Template Rendering → PureMix engine processes HTML with AST-based template interpreter
  6. Client Runtime → Injects browser-side PureMix API for AJAX and component updates
  7. HTML Response → Express sends complete server-rendered HTML to browser

Key Technologies

  • Express.js 4.x - Web server and middleware
  • TypeScript - Type safety (compiled to JavaScript by Node.js 22+)
  • Python 3.8+ - Optional for data science and ML integration
  • AST Parser - Custom template engine using Abstract Syntax Trees (no regex)
  • DOM Diffing - Client-side smart updates inspired by React

Installation

Install PureMix globally using npm:

npm install -g puremix@alpha

Or use npx to run commands without installing:

npx puremix@alpha create my-app

Requirements

  • Node.js 22+ - Native TypeScript support
  • Python 3.8+ (optional) - For Python integration features

Quick Start

Create a New Project

puremix create my-app
cd my-app
npm install
npm run dev

Your app will be running at http://localhost:3000

Available Templates

  • basic - Modern Tailwind CSS with animations
  • minimal - Clean responsive CSS, zero dependencies
  • advanced - Full MongoDB + authentication + admin panel
puremix create my-app --template basic

Project Structure

my-app/
├── app/
│   ├── routes/           # File-based routing
│   │   ├── index.puremix # Home page (/)
│   │   ├── about.puremix # About page (/about)
│   │   └── products/
│   │       ├── index.puremix  # /products
│   │       └── [id].puremix   # /products/:id
│   ├── components/       # Reusable components
│   ├── layouts/          # Layout templates
│   ├── lib/              # Utility functions
│   └── services/         # Business logic & Python modules
├── public/               # Static assets
├── puremix.config.js     # Framework configuration
└── package.json

File-Based Routing

PureMix uses file structure to define URL routes automatically - inspired by Remix's routing system. No configuration needed.

Basic Routes

File Path URL Route
app/routes/index.puremix /
app/routes/about.puremix /about
app/routes/products/index.puremix /products

Dynamic Parameters

Use square brackets [param] for dynamic route segments:

File Path URL Pattern Example
products/[id].puremix /products/:id /products/123
blog/[category]/[slug].puremix /blog/:category/:slug /blog/tech/ai-revolution
docs/[...slug].puremix /docs/* (catch-all) /docs/api/users/create

Example: Dynamic Route

<!-- File: app/routes/products/[id].puremix -->
<loader>
  async function loadProduct(request) {
    const productId = request.params.id; // Extract from URL
    const product = await getProduct(productId);

    return {
      data: { product }
    };
  }
</loader>

<div>
  <h1>{loadProduct.data.product.name}</h1>
  <p>Product ID: {params.id}</p>
</div>

Query Parameters

Query parameters are automatically available via request.query:

// URL: /products/123?category=electronics&sort=price
async function loadProduct(request) {
  const productId = request.params.id;      // "123"
  const category = request.query.category;  // "electronics"
  const sortBy = request.query.sort;        // "price"

  // Use in your logic
  const product = await getProduct(productId);
  const related = await getRelatedProducts(category, sortBy);

  return { data: { product, related } };
}

Loaders (Data Fetching)

Loaders run on the server before the page renders. They fetch data from databases, APIs, or any other source and make it available to your templates.

⚠️ Critical Concept: The actionResult parameter contains the return value from any action (server function) that executed before the loader. This is how PureMix handles the Remix pattern: Action → Loader → Render.

Basic Loader

<loader>
  async function loadPage(request, actionResult) {
    // Fetch data from database, API, etc.
    const products = await getProducts();

    // Check if an action just ran
    const message = actionResult?.success
      ? 'Product saved successfully!'
      : null;

    return {
      data: { products, message }
    };
  }
</loader>

<div>
  {loadPage.data.message &&
    <div class="alert">{loadPage.data.message}</div>
  }

  <h1>Products</h1>
  {loadPage.data.products.map(product =>
    <div class="product">{product.name}</div>
  )}
</div>

Accessing Loader Data in Templates

Loader data is available using the loader function name:

{loadPage.data.user.name}
{loadDashboard.data.stats.total}
{loadProducts.data.items.length}

Request Object

The request parameter provides access to:

  • request.params - URL parameters (e.g., {id} from [id].puremix)
  • request.query - Query string parameters
  • request.method - HTTP method (GET, POST, etc.)
  • request.headers - HTTP headers
  • request.cookies - Parsed cookies
  • request.body - Request body (for POST requests)

Actions (Mutations)

Actions are server-side functions that handle mutations (create, update, delete operations). They run in <script server> blocks and are triggered by forms or AJAX calls.

⚠️ Important: When an action runs, its return value is passed to the loader as actionResult. The loader then re-executes with this data, allowing you to show success messages, updated data, or handle errors.

Basic Action

<loader>
  async function loadProducts(request, actionResult) {
    const products = await getProducts();
    const message = actionResult?.success
      ? 'Product created successfully!'
      : actionResult?.error;

    return {
      data: { products, message }
    };
  }
</loader>

<div>
  {loadProducts.data.message &&
    <div class="alert">{loadProducts.data.message}</div>
  }

  <form onsubmit="createProduct">
    <input name="name" placeholder="Product name" required>
    <input name="price" type="number" placeholder="Price" required>
    <button type="submit">Create Product</button>
  </form>
</div>

<script server>
  async function createProduct(formData, request) {
    try {
      const product = await saveProduct({
        name: formData.get('name'),
        price: parseFloat(formData.get('price'))
      });

      return {
        success: true,
        product
      };
    } catch (error) {
      return {
        success: false,
        error: error.message
      };
    }
  }
</script>

ActionResult Flow-Through Pattern

The loader receives actionResult as the second parameter, allowing conditional data loading based on action outcomes:

<loader>
  async function loadPage(request, actionResult) {
    // Check if coming from an action
    if (actionResult && actionResult.success) {
      return {
        data: {
          message: "Operation succeeded!",
          result: actionResult.data,
          timestamp: new Date().toISOString()
        },
        state: { showSuccess: true }
      };
    }

    // Check for errors from action
    if (actionResult && actionResult.error) {
      return {
        data: { message: actionResult.error },
        state: { showError: true }
      };
    }

    // Default data when no action was triggered
    return {
      data: await loadDefaultData(),
      state: { showSuccess: false, showError: false }
    };
  }
</loader>

Form Handling

Connect forms to actions using the onsubmit attribute:

<form onsubmit="handleSubmit">
  <!-- Form fields automatically extracted as FormData -->
  <input name="email" type="email">
  <button type="submit">Submit</button>
</form>

<script server>
  async function handleSubmit(formData, request) {
    const email = formData.get('email');
    // Process form submission
    return { success: true };
  }
</script>

AJAX Actions

Call actions from client-side JavaScript using the PureMix.call() API:

<button onclick="handleClick()">Update</button>

<script>
  async function handleClick() {
    const result = await PureMix.call('updateData', {
      userId: 123,
      status: 'active'
    });

    if (result.success) {
      console.log('Update successful!');
    }
  }
</script>

<script server>
  async function updateData(data, request) {
    await updateUser(data.userId, { status: data.status });
    return { success: true };
  }
</script>

Templates & Expressions

PureMix templates use curly braces {} for dynamic expressions. The template engine is AST-based (completely regex-free) for reliable parsing.

Simple Expressions

<div>
  <h1>{user.name}</h1>
  <p>{user.email}</p>
  <p>Total: ${product.price}</p>
</div>

Conditional Rendering

Both HTML elements and string literals work in conditional expressions:

<!-- HTML element conditionals -->
{user.isActive ?
  <div>Active User</div> :
  <div>Inactive</div>
}

<!-- String literal conditionals -->
<p>Status: {user.isActive ? "Active" : "Inactive"}</p>
<p>Theme: {user.theme === 'dark' ? '🌙 Dark' : '☀️ Light'}</p>

<!-- Boolean conditions -->
{user.isAdmin && <div>Admin Panel</div>}

<!-- Nested conditions (multiple levels supported) -->
{user.role === 'admin' ?
  <div>Admin Dashboard</div> :
  user.role === 'moderator' ?
    <div>Moderator Panel</div> :
    <div>User Profile</div>
}
✅ Flexible Conditionals: You can return HTML elements, string literals, or mix both patterns. The template engine handles all conditional expression types.

Array Mapping

{products.map(product =>
  <div class="product-card">
    <h3>{product.name}</h3>
    <p>${product.price}</p>
    <button onclick="addToCart">Add to Cart</button>
  </div>
)}

JavaScript Blocks

Execute complex JavaScript within templates and export variables:

{
  // Full JavaScript execution
  let activeUsers = users.filter(u => u.isActive);
  let adminCount = activeUsers.filter(u => u.role === 'admin').length;

  function formatRole(role) {
    return role.charAt(0).toUpperCase() + role.slice(1);
  }

  // Export variables to template
  __export = { activeUsers, adminCount, formatRole };
}

<div>
  <h2>Active Users: {activeUsers.length} (Admins: {adminCount})</h2>
  {activeUsers.map(user =>
    <div>{user.name} - {formatRole(user.role)}</div>
  )}
</div>

Template Processing Boundaries

⚠️ Critical Rule: Template expressions {...} are only processed in HTML templates, NOT in script tags.

Understanding where template expressions are processed is essential for correct usage:

Context Template Processing Example
HTML Templates ✅ Processed <h1>{user.name}</h1>
Client Scripts ❌ NOT Processed <script> tags
Server Scripts ❌ NOT Processed <script server> tags

Accessing Loader Data in Client Scripts

Client scripts must use PureMix.data to access loader data:

// ❌ WRONG: Template expressions don't work in scripts
const name = '{user.name}';  // This stays as literal string!

// ✅ CORRECT: Use PureMix.data
const userData = PureMix.data.loadPage?.data?.user;
const name = userData?.name || 'Guest';

// Access loader data safely
if (PureMix.data && PureMix.data.loadPage) {
  console.log('User:', PureMix.data.loadPage.data.user.name);
}
🔒 Security Note: This separation prevents unintended server data exposure and maintains clear boundaries between server and client contexts.

Components

Components are reusable .puremix files that can be imported and used in other pages or components. They support props, server functions, and client scripts.

Creating a Component

<!-- File: app/components/UserCard.puremix -->
<div class="user-card">
  <img src="{user.avatar}" alt="{user.name}">
  <h3>{user.name}</h3>
  <p>{user.email}</p>

  {isEditable &&
    <button onclick="updateUser">Edit</button>
  }
</div>

<script server>
  async function updateUser(formData, request) {
    // Component-scoped server function
    const userId = formData.get('userId');
    await updateUserData(userId);
    return { success: true };
  }
</script>

<style>
  .user-card {
    border: 1px solid #ddd;
    padding: 1rem;
    border-radius: 8px;
  }
</style>

Using Components

<imports>
  import UserCard from '../components/UserCard.puremix'
  import ProductList from '../components/ProductList.puremix'
</imports>

<loader>
  async function loadPage(request) {
    const users = await getUsers();
    return { data: { users } };
  }
</loader>

<div>
  <h1>Team Members</h1>
  {loadPage.data.users.map(user =>
    <UserCard user={user} isEditable={true} />
  )}
</div>

Component Props

Pass data to components using JSX-style syntax. Components receive props as the third parameter in their loader function:

<!-- Parent page: Passing props -->
<UserCard
  user={userData}
  isActive={true}
  config={{theme: "dark", showEmail: true}}
/>

<!-- Component file: UserCard.puremix -->
<loader>
  async function loadUserCard(request, actionResult, props) {
    // Props are passed as third parameter!
    const user = props?.user || { name: 'Guest', email: 'guest@example.com' };
    const isActive = props?.isActive ?? false;
    const config = props?.config || {};

    return {
      data: { user, isActive, config }
    };
  }
</loader>

<div class="user-card">
  <h3>{loadUserCard.data.user.name}</h3>
  {loadUserCard.data.isActive && <span class="badge">Active</span>}
  {loadUserCard.data.config.showEmail && <p>{loadUserCard.data.user.email}</p>}
</div>
💡 Props Pattern: The third parameter in the component's loader function receives all props passed from the parent. Use optional chaining (props?.user) for safe access and provide fallback values.

Component Server Functions

Component server functions are automatically namespaced with the component name to avoid conflicts:

// Automatically available:
// - ComponentName.functionName
// - updateUser (if unique globally)

// Call from forms:
<form onsubmit="UserCard.updateUser">

// Call from JavaScript:
await PureMix.call('UserCard.updateUser', data);

Python Integration Overview

PureMix provides seamless Python integration as a first-class language alongside JavaScript and TypeScript. Python functions can be called exactly like JavaScript functions, with automatic process pooling and graceful fallbacks.

Six Integration Methods

1. Script Tags

Embed Python directly in .puremix files

2. Standalone Modules

Auto-discovered .py files

3. ES6 Imports

Import Python like JavaScript modules

4. Global Functions

Call Python functions globally without imports

5. Inline Execution

Dynamic Python code execution

6. ML Libraries

Direct NumPy, Pandas, Scikit-learn access

Python Process Architecture

  • Process Pool: 4 dedicated Python workers for parallel execution
  • Auto-Discovery: Recursive scanning of app/ directory at startup
  • Graceful Fallbacks: Framework continues working if Python unavailable
  • Context Sharing: JavaScript data seamlessly passed to Python functions
# Server startup logs
🚀 PYTHON STARTUP: Spawning 4 worker processes...
🐍 Python executor initialized: 4 workers ready
🔍 RECURSIVE PYTHON SCAN: Discovering modules in /app/...
   📜 services/financial_analyzer: calculate_loan, analyze_portfolio
   📜 lib/data_processor: validate_data, process_data
🐍 Python modules: 2/2 modules (4 functions)

Python Script Tags

Embed Python functions directly in your .puremix files using <script server lang="python"> tags.

Basic Example

<loader>
  async function loadDashboard(request) {
    const salesData = await getSalesData();

    // Call Python function
    const analysis = await analyze_sales({ sales: salesData });

    return {
      data: { salesData, analysis }
    };
  }
</loader>

<div>
  <h1>Sales Dashboard</h1>
  <p>Average: ${analysis.average}</p>
  <p>Total: ${analysis.total}</p>
</div>

<script server lang="python">
def analyze_sales(data, js_context=None):
    """Analyze sales data using Python"""
    import statistics

    sales = [item['amount'] for item in data.get('sales', [])]

    return {
        'success': True,
        'average': statistics.mean(sales) if sales else 0,
        'total': sum(sales),
        'count': len(sales)
    }
</script>

With Pandas and NumPy

<script server lang="python">
def calculate_loan(data, js_context=None):
    """Calculate loan amortization with pandas"""
    import pandas as pd
    import numpy as np

    principal = data.get('principal', 0)
    rate = data.get('rate', 0) / 100 / 12
    years = data.get('years', 0)
    months = years * 12

    # Monthly payment calculation
    if rate == 0:
        payment = principal / months
    else:
        payment = principal * (rate * (1 + rate)**months) / ((1 + rate)**months - 1)

    # Create amortization schedule
    schedule = []
    balance = principal

    for month in range(1, months + 1):
        interest = balance * rate
        principal_payment = payment - interest
        balance -= principal_payment

        schedule.append({
            'month': month,
            'payment': round(payment, 2),
            'principal': round(principal_payment, 2),
            'interest': round(interest, 2),
            'balance': round(max(balance, 0), 2)
        })

    df = pd.DataFrame(schedule)

    return {
        'success': True,
        'monthly_payment': round(payment, 2),
        'total_paid': round(payment * months, 2),
        'total_interest': round((payment * months) - principal, 2),
        'schedule': df.to_dict('records')
    }
</script>

Function Signature

Python server functions should follow this pattern:

def function_name(data, js_context=None):
    """
    Args:
        data: Input data from JavaScript (dict)
        js_context: Optional additional context from JavaScript

    Returns:
        dict: Result object (must be JSON-serializable)
    """
    # Your Python code here

    return {
        'success': True,
        'result': processed_data
    }

Standalone Python Modules

Create independent Python files in your app/ directory. PureMix automatically discovers and registers all Python modules at server startup.

Creating a Module

# File: app/services/financial_analyzer.py

def calculate_loan_amortization(data, js_context=None):
    """Calculate loan amortization schedule"""
    principal = data.get('principal', 0)
    rate = data.get('rate', 0) / 100 / 12
    years = data.get('years', 0)

    # Complex calculations...

    return {
        'success': True,
        'monthly_payment': monthly_payment,
        'total_interest': total_interest,
        'schedule': payment_schedule
    }

def analyze_investment_portfolio(data, js_context=None):
    """Analyze investment portfolio performance"""
    import pandas as pd
    import numpy as np

    df = pd.DataFrame(data.get('investments', []))

    return {
        'success': True,
        'total_value': float(df['value'].sum()),
        'roi': float(df['roi'].mean())
    }

Using Python Modules

// Method 1: Direct execution
const analysis = await request.python.executeFile(
  './app/services/financial_analyzer.py',
  'calculate_loan_amortization',
  { principal: 300000, rate: 4.5, years: 30 }
);

Auto-Discovery at Startup

🔍 RECURSIVE PYTHON SCAN: Discovering modules in /app/...
   📜 controllers/user_controller: get_user_profile, update_user_settings
   📜 lib/data_processor: process_data, validate_data
   📜 services/financial_analyzer: calculate_loan, analyze_portfolio
   📜 services/ml_analyzer: analyze_dataset, train_model
🐍 Python modules: 4/4 modules (12 functions)

ES6 Python Imports

Revolutionary feature: Import Python modules using standard ES6 import syntax. Python functions become callable exactly like JavaScript functions.

Import Python Like JavaScript

<imports>
  import { validate_data, process_data } from '../lib/data_processor'
  import { calculate_loan } from '../services/financial_analyzer'
  import { format_text } from '../lib/utils/string_helpers'
</imports>

<loader>
  async function loadPage(request) {
    // Call Python functions exactly like JavaScript!
    const validation = await validate_data({
      user_id: request.params.id,
      email: request.query.email
    });

    const processed = await process_data(validation);

    const loan = await calculate_loan({
      principal: 300000,
      rate: 4.5,
      years: 30
    });

    return {
      data: { validation, processed, loan }
    };
  }
</loader>

How It Works

  • Smart Module Resolution: ImportResolver detects .py files automatically
  • Pre-loaded References: Python modules loaded at server startup (no import overhead)
  • Language Transparency: No difference between calling Python or JavaScript functions
  • Type Safety: Return values automatically unwrapped from Python execution context

Global Python Functions

Ultimate convenience: All Python functions are automatically available globally without any imports. Just call them like regular JavaScript functions.

No Imports Required

<loader>
  async function loadPage(request) {
    // Call Python functions directly - no imports needed!
    const validation = await validate_data({
      user_id: 12345
    });

    const processed = await process_data(validation);

    const formatted = await format_text({
      text: 'hello world'
    });

    return {
      data: { validation, processed, formatted }
    };
  }
</loader>

Global Function Registry

At server startup, all Python functions are registered globally:

🔍 FILE PARSER: Adding 36 global Python functions to compilation context
📋 Available functions:
   - validate_data
   - process_data
   - calculate_loan
   - analyze_portfolio
   - format_text
   - ...

Architecture Benefits

  • Zero Configuration: Functions available immediately after server starts
  • No Import Overhead: Pre-registered at startup for instant access
  • Namespace Protection: Conflicts handled with module_function fallback
  • Performance: Direct process pool access without module resolution

Inline Python Execution

Execute dynamic Python code at runtime using request.python.call(). Perfect for ad-hoc calculations or runtime code generation.

Basic Inline Execution

const result = await request.python.call('analyze_data', inputData, `
  import pandas as pd
  import numpy as np

  def analyze_data(data, js_context=None):
    df = pd.DataFrame(data)

    return {
      'success': True,
      'mean': float(df['values'].mean()),
      'std': float(df['values'].std()),
      'analysis': 'Complete'
    }
`);

With Complex Logic

<loader>
  async function loadAnalytics(request) {
    const rawData = await getRawData();

    // Execute custom Python analysis
    const analysis = await request.python.call('custom_analysis', rawData, `
      import pandas as pd
      import numpy as np
      from sklearn.linear_model import LinearRegression

      def custom_analysis(data, js_context=None):
        df = pd.DataFrame(data.get('records', []))

        # Feature engineering
        df['date'] = pd.to_datetime(df['date'])
        df['day_of_week'] = df['date'].dt.dayofweek

        # Simple linear regression
        X = df[['day_of_week']].values
        y = df['sales'].values

        model = LinearRegression()
        model.fit(X, y)

        predictions = model.predict(X)

        return {
          'success': True,
          'coefficients': model.coef_.tolist(),
          'predictions': predictions.tolist(),
          'r_squared': float(model.score(X, y))
        }
    `);

    return {
      data: { rawData, analysis: analysis.data }
    };
  }
</loader>

Use Cases

  • Dynamic data analysis with user-defined parameters
  • Runtime code generation and execution
  • Testing Python code without creating files
  • Prototyping ML models before moving to modules

ML Library Integration

PureMix provides built-in interfaces for popular Python ML libraries: NumPy, Pandas, Scikit-learn, and TensorFlow.

Available Libraries

// NumPy operations
const array = await request.python.numpy.array([1, 2, 3, 4, 5]);

// Pandas DataFrame operations
const df = await request.python.pandas.DataFrame(data);

// Scikit-learn model training
const model = await request.python.sklearn.train(features, labels);

// TensorFlow operations
const tensor = await request.python.tensorflow.constant([1, 2, 3]);

Complete ML Example

<loader>
  async function loadMLDashboard(request) {
    const historicalData = await getHistoricalData();

    // Call Python for ML analysis
    const prediction = await predict_time_series(historicalData);

    return {
      data: { historicalData, prediction }
    };
  }
</loader>

<script server lang="python">
def predict_time_series(data, js_context=None):
    """Time series prediction with pandas and numpy"""
    import pandas as pd
    import numpy as np
    from sklearn.linear_model import LinearRegression

    # Create DataFrame
    df = pd.DataFrame(data.get('records', []))
    df['date'] = pd.to_datetime(df['date'])
    df = df.sort_values('date')

    # Feature engineering
    df['timestamp'] = df['date'].astype(int) / 10**9
    df['day_of_week'] = df['date'].dt.dayofweek
    df['month'] = df['date'].dt.month

    # Prepare features and target
    X = df[['timestamp', 'day_of_week', 'month']].values
    y = df['value'].values

    # Train model
    model = LinearRegression()
    model.fit(X, y)

    # Make predictions for next 7 days
    last_date = df['date'].max()
    future_dates = pd.date_range(
        start=last_date + pd.Timedelta(days=1),
        periods=7,
        freq='D'
    )

    future_features = np.array([
        [date.timestamp(), date.dayofweek, date.month]
        for date in future_dates
    ])

    predictions = model.predict(future_features)

    return {
        'success': True,
        'predictions': [
            {
                'date': str(date.date()),
                'value': float(pred)
            }
            for date, pred in zip(future_dates, predictions)
        ],
        'r_squared': float(model.score(X, y)),
        'coefficients': model.coef_.tolist()
    }
</script>

API Handlers

PureMix supports building APIs of any type (REST, RPC, GraphQL, etc.) using handler functions in JavaScript, TypeScript, or Python.

Handler Discovery Priority

PureMix searches for handler functions in this order:

  1. export default async function handler(req, res)
  2. export async function GET(req, res) (or POST, PUT, DELETE)
  3. export async function get(req, res) (lowercase)
  4. export async function handle/process/execute(req, res)
  5. def main(context) (Python)

Single Handler Pattern

// File: app/routes/api/users.js
export default async function handler(request, response) {
  if (request.method !== 'GET') {
    return response.status(405).json({ error: 'Method not allowed' });
  }

  const users = await getUsers();
  return response.status(200).json({
    success: true,
    data: users
  });
}

Multi-Method Handler

// File: app/routes/api/products/[id].js
export default async function handler(request, response) {
  const { method } = request;
  const { id } = request.params;

  if (method === 'GET') {
    const product = await getProduct(id);
    return response.json({ data: product });
  }

  if (method === 'PUT' || method === 'PATCH') {
    const updated = await updateProduct(id, request.body);
    return response.json({ data: updated });
  }

  if (method === 'DELETE') {
    await deleteProduct(id);
    return response.status(204).end();
  }

  return response.status(405).json({ error: 'Method not allowed' });
}

Separate Method Exports

// File: app/routes/api/products.js
export async function GET(request, response) {
  const products = await getProducts();
  return response.json({ data: products });
}

export async function POST(request, response) {
  const newProduct = await createProduct(request.body);
  return response.status(201).json({ data: newProduct });
}

REST APIs

Build RESTful APIs using file-based routing and standard HTTP methods.

REST Resource Pattern

app/routes/api/
├── products/
│   ├── index.js          # GET /api/products, POST /api/products
│   └── [id].js           # GET/PUT/DELETE /api/products/:id
├── users/
│   ├── index.js          # User collection
│   └── [id]/
│       ├── index.js      # Single user
│       └── profile.js    # /api/users/:id/profile

Complete REST Example

// File: app/routes/api/products/[id].js
export default async function handler(request, response) {
  const { method } = request;
  const { id } = request.params;

  // GET /api/products/:id
  if (method === 'GET') {
    try {
      const product = await db.products.findById(id);

      if (!product) {
        return response.status(404).json({
          error: 'Product not found'
        });
      }

      return response.json({
        success: true,
        data: product
      });
    } catch (error) {
      return response.status(500).json({
        error: error.message
      });
    }
  }

  // PUT /api/products/:id
  if (method === 'PUT') {
    try {
      const updated = await db.products.update(id, request.body);

      return response.json({
        success: true,
        data: updated
      });
    } catch (error) {
      return response.status(400).json({
        error: error.message
      });
    }
  }

  // DELETE /api/products/:id
  if (method === 'DELETE') {
    try {
      await db.products.delete(id);
      return response.status(204).end();
    } catch (error) {
      return response.status(500).json({
        error: error.message
      });
    }
  }

  return response.status(405).json({
    error: 'Method not allowed'
  });
}

RPC APIs

Build RPC-style APIs where methods are called via a single endpoint.

RPC Endpoint

// File: app/routes/api/rpc.js
export default async function handler(request, response) {
  if (request.method !== 'POST') {
    return response.status(405).json({ error: 'POST required' });
  }

  const { method, params } = request.body;

  switch (method) {
    case 'user.create':
      return response.json(await createUser(params));

    case 'user.get':
      return response.json(await getUser(params));

    case 'user.update':
      return response.json(await updateUser(params));

    case 'order.place':
      return response.json(await placeOrder(params));

    case 'order.status':
      return response.json(await getOrderStatus(params));

    default:
      return response.status(400).json({
        error: 'Unknown method'
      });
  }
}

async function createUser(params) {
  const user = await db.users.create({
    email: params.email,
    name: params.name
  });

  return {
    success: true,
    userId: user.id
  };
}

async function getUser(params) {
  const user = await db.users.findById(params.userId);

  return {
    success: true,
    user
  };
}

Client Usage

// Client-side RPC calls
async function callRPC(method, params) {
  const response = await fetch('/api/rpc', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ method, params })
  });

  return response.json();
}

// Create user
const result = await callRPC('user.create', {
  email: 'user@example.com',
  name: 'John Doe'
});

// Get user
const user = await callRPC('user.get', {
  userId: result.userId
});

Python API Handlers

Build APIs using Python for data-intensive operations, ML inference, or when you prefer Python's ecosystem.

Python API Handler

# File: app/routes/api/analyze.py
import json

def main(context):
    """Python API handler"""
    request = context.get('request', {})
    method = request.get('method', 'GET').upper()

    if method != 'POST':
        return {
            'status': 405,
            'headers': {'Content-Type': 'application/json'},
            'body': json.dumps({'error': 'Method not allowed'})
        }

    # Process data with Python libraries
    data = request.get('body', {})
    result = process_data(data)

    return {
        'status': 200,
        'headers': {'Content-Type': 'application/json'},
        'body': json.dumps({
            'success': True,
            'data': result
        })
    }

def process_data(data):
    """Process data with pandas/numpy"""
    import pandas as pd
    import numpy as np

    df = pd.DataFrame(data.get('records', []))

    return {
        'mean': float(df['value'].mean()),
        'std': float(df['value'].std()),
        'count': len(df)
    }

ML Inference API

# File: app/routes/api/ml/predict.py
import json
import numpy as np
from sklearn.linear_model import LinearRegression

# Load pre-trained model (in production, load from file)
model = LinearRegression()

def main(context):
    """ML prediction API endpoint"""
    request = context.get('request', {})

    if request.get('method') != 'POST':
        return error_response('POST required', 405)

    try:
        # Get features from request
        data = request.get('body', {})
        features = np.array(data.get('features', [])).reshape(1, -1)

        # Make prediction
        prediction = model.predict(features)

        return {
            'status': 200,
            'headers': {'Content-Type': 'application/json'},
            'body': json.dumps({
                'success': True,
                'prediction': float(prediction[0]),
                'model': 'LinearRegression'
            })
        }
    except Exception as e:
        return error_response(str(e), 400)

def error_response(message, status=400):
    return {
        'status': status,
        'headers': {'Content-Type': 'application/json'},
        'body': json.dumps({
            'success': False,
            'error': message
        })
    }

Python Context Structure

context = {
    'request': {
        'method': 'POST',
        'params': { 'id': '123' },
        'query': { 'page': '1' },
        'body': { 'data': [...] },
        'headers': { 'Content-Type': 'application/json' },
        'cookies': { 'sessionId': 'abc' }
    }
}

JavaScript Blocks

Execute complex JavaScript within templates and export variables for use in HTML. This feature provides full programming language capabilities within your templates.

Basic JavaScript Block

{
  // Full JavaScript execution
  let numbers = [1, 2, 3, 4, 5];
  let doubled = numbers.map(n => n * 2);
  let sum = doubled.reduce((acc, val) => acc + val, 0);

  // Export to template
  __export = { doubled, sum };
}

<div>
  <p>Doubled: {doubled.join(', ')}</p>
  <p>Sum: {sum}</p>
</div>

Complex Data Processing

{
  // Filter and transform data
  let activeUsers = users.filter(u => u.isActive);
  let usersByRole = activeUsers.reduce((acc, user) => {
    acc[user.role] = acc[user.role] || [];
    acc[user.role].push(user);
    return acc;
  }, {});

  // Helper functions
  function formatRole(role) {
    return role.charAt(0).toUpperCase() + role.slice(1);
  }

  function calculateTotal(users) {
    return users.reduce((sum, u) => sum + u.sales, 0);
  }

  // Export everything needed
  __export = {
    activeUsers,
    usersByRole,
    formatRole,
    calculateTotal
  };
}

<div>
  <h2>Active Users: {activeUsers.length}</h2>

  {Object.keys(usersByRole).map(role =>
    <div class="role-section">
      <h3>{formatRole(role)}: {usersByRole[role].length}</h3>
      <p>Total Sales: ${calculateTotal(usersByRole[role])}</p>

      {usersByRole[role].map(user =>
        <div class="user-card">
          <h4>{user.name}</h4>
          <p>Sales: ${user.sales}</p>
        </div>
      )}
    </div>
  )}
</div>

Available JavaScript Features

  • Variables: let, const, var
  • Functions: Regular and arrow functions
  • Loops: for, while, for...of
  • Conditionals: if/else, switch
  • Array Methods: .map(), .filter(), .reduce()
  • Object Operations: Object.keys(), Object.entries()

Architecture

JavaScript blocks are executed in isolated Node.js closures for security. The execution environment has access to template data but not to Node.js globals like fs or process.

Best Practices

Production-ready patterns and recommendations based on real-world usage:

Safe Data Access

Use optional chaining to prevent errors when accessing nested data:

<!-- ✅ RECOMMENDED: Optional chaining -->
<h1>{loadPage?.data?.user?.name || 'Guest'}</h1>
<p>{loadPage?.data?.user?.profile?.bio || 'No bio available'}</p>

<!-- ❌ AVOID: Direct access without safety -->
<h1>{loadPage.data.user.name}</h1>  <!-- Error if user is undefined -->

Error Handling in Loaders

Wrap async operations in try-catch blocks for graceful error handling:

async function loadPage(request) {
  try {
    const data = await fetchData();
    const analysis = await processData(data);

    return {
      data: { analysis, success: true }
    };
  } catch (error) {
    console.error('Loader error:', error.message);

    return {
      data: {
        error: error.message,
        success: false
      },
      state: { hasError: true }
    };
  }
}

Python Fallback Handling

Handle cases where Python libraries may not be available:

async function loadAnalytics(request) {
  try {
    const result = await analyze_dataset(data);

    if (result.__pythonUnavailable || result.__pythonError) {
      // Fallback to JavaScript implementation
      return {
        data: { result: await jsAnalyze(data), method: 'JavaScript' }
      };
    }

    return { data: { result, method: 'Python' } };
  } catch (error) {
    return { data: { error: error.message } };
  }
}

Component Isolation

Keep components self-contained with their own data fetching:

<!-- Component loader handles its own data -->
<loader>
  async function loadComponent(request, actionResult, props) {
    // Use props if provided, otherwise fetch default data
    const data = props?.data || await fetchDefaultData();

    return {
      data: { ...data },
      state: { initialized: true }
    };
  }
</loader>

Form Validation

Validate data both client-side and server-side:

<script server>
  async function handleSubmit(formData, request) {
    const email = formData.get('email');
    const name = formData.get('name');

    // Server-side validation
    if (!email || !email.includes('@')) {
      return {
        success: false,
        error: 'Invalid email address'
      };
    }

    if (!name || name.trim().length < 2) {
      return {
        success: false,
        error: 'Name must be at least 2 characters'
      };
    }

    // Process valid data
    const result = await saveUser({ email, name });
    return { success: true, data: result };
  }
</script>

Performance Optimization

  • Minimize loader complexity: Keep loaders focused and fast
  • Use component selective updates: Update only what changed
  • Cache expensive operations: Store results when appropriate
  • Leverage Python process pools: Let the framework handle parallelization
  • Optional chaining: Reduces runtime errors and improves stability

Security Considerations

  • Validate all user input: Never trust client data
  • Use parameterized queries: Prevent SQL injection
  • Sanitize HTML output: Framework handles this automatically
  • Check authentication in loaders: Verify request.session.user
  • Rate limit server functions: Prevent abuse of AJAX endpoints

Client Runtime

PureMix automatically generates a client-side runtime (window.PureMix) that provides AJAX functionality, loader data access, and server function calls.

PureMix.call() - AJAX Server Functions

// Call server functions from client-side
const result = await PureMix.call('updateUser', {
  userId: 123,
  name: 'Updated Name'
});

if (result.success) {
  console.log('User updated!');
}

PureMix.data - Loader Data Access

// Access loader data in client scripts
const userData = PureMix.data.loadPage.data.user;
const settings = PureMix.data.loadDashboard.data.settings;

console.log('Current user:', userData.name);
console.log('Theme:', settings.theme);

Complete Client Example

<loader>
  async function loadUserProfile(request) {
    const user = await getUser(request.params.id);
    const settings = await getSettings(user.id);

    return {
      data: { user, settings }
    };
  }
</loader>

<div>
  <h1 id="user-name">{loadUserProfile.data.user.name}</h1>
  <button onclick="handleUpdate()">Update Name</button>
</div>

<script>
  // Client-side JavaScript
  document.addEventListener('DOMContentLoaded', function() {
    // Access loader data
    const user = PureMix.data.loadUserProfile.data.user;
    console.log('Loaded user:', user.name);

    // Apply theme from settings
    const theme = PureMix.data.loadUserProfile.data.settings.theme;
    if (theme === 'dark') {
      document.body.classList.add('dark-theme');
    }
  });

  async function handleUpdate() {
    const result = await PureMix.call('updateUserName', {
      userId: PureMix.data.loadUserProfile.data.user.id,
      name: 'New Name'
    });

    if (result.success) {
      // Update UI
      document.getElementById('user-name').textContent = result.user.name;
    }
  }
</script>

<script server>
  async function updateUserName(data, request) {
    const user = await updateUser(data.userId, {
      name: data.name
    });

    return { success: true, user };
  }
</script>

API Reference

  • PureMix.call(functionName, data) - Call server functions via AJAX
  • PureMix.data - Access loader results in client scripts
  • PureMix.data.{loaderName}.data - Loader data object
  • PureMix.data.{loaderName}.state - Loader state object (optional)

DOM Diffing Algorithm

PureMix implements a sophisticated client-side DOM diffing algorithm that provides React-like update experience without page reloads. This eliminates visual flicker during AJAX updates.

Key Features

  • Node-Level Diffing: Only changed DOM nodes are updated
  • Form State Preservation: Maintains focus, cursor position, and user input
  • Scroll Position Recovery: Preserves scroll positions across updates
  • Attribute Synchronization: Efficiently updates only changed attributes
  • Performance: Sub-10ms update times for typical components

How It Works

// Automatic diffing during component updates
// File: lib/client-runtime.ts

function diffAndUpdateDOM(oldElement, newHtml) {
  // 1. Preserve scroll positions
  const scrollPositions = preserveScrollPositions(oldElement);

  // 2. Parse new HTML
  const tempDiv = document.createElement('div');
  tempDiv.innerHTML = newHtml;
  const newElement = tempDiv.firstElementChild;

  // 3. Diff nodes recursively
  diffNodes(oldElement, newElement);

  // 4. Restore scroll positions
  restoreScrollPositions(oldElement, scrollPositions);
}

Component Updates

When components update via AJAX, the diffing algorithm ensures smooth transitions:

<div id="counter-component">
  <h2>Count: {count}</h2>
  <button onclick="increment">Increment</button>
</div>

<script server>
  let count = 0;

  async function increment(formData, request) {
    count++;
    return { success: true, count };
  }
</script>

<script>
  // When increment is called:
  // 1. Server returns updated count
  // 2. Component HTML re-rendered
  // 3. DOM diffing updates ONLY the count value
  // 4. No flicker, no full page reload
</script>

Performance Characteristics

  • Average Update Time: 6-10ms for typical components
  • Memory Efficient: Minimal DOM traversal with intelligent caching
  • Form Friendly: Preserves active element focus and selection
  • Scroll Aware: Maintains scroll state across all scrollable elements

Layouts

Layouts wrap your pages with common HTML structure (headers, footers, navigation). Define layouts once and reuse across multiple pages.

Creating a Layout

<!-- File: app/layouts/main.puremix -->
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>{title || 'My App'}</title>
  <link rel="stylesheet" href="/styles/main.css">
</head>
<body>
  <nav class="navbar">
    <a href="/">Home</a>
    <a href="/about">About</a>
    <a href="/products">Products</a>
  </nav>

  <main>
    {children}
  </main>

  <footer>
    <p>© 2025 My App</p>
  </footer>
</body>
</html>

Using Layouts

<!-- File: app/routes/index.puremix -->
<layout>main</layout>

<loader>
  async function loadPage(request) {
    return {
      data: {
        title: 'Home Page',
        message: 'Welcome!'
      }
    };
  }
</loader>

<div>
  <h1>{loadPage.data.message}</h1>
  <p>This content will be wrapped in the main layout</p>
</div>

Nested Layouts

<!-- File: app/layouts/dashboard.puremix -->
<layout>main</layout>

<div class="dashboard-layout">
  <aside class="sidebar">
    <a href="/dashboard">Overview</a>
    <a href="/dashboard/settings">Settings</a>
    <a href="/dashboard/analytics">Analytics</a>
  </aside>

  <div class="dashboard-content">
    {children}
  </div>
</div>

Dynamic Layout Properties

<!-- Pass data to layout from page -->
<layout>main</layout>

<loader>
  async function loadPage(request) {
    return {
      data: {
        title: 'Product Details',
        showNav: true,
        theme: 'dark'
      }
    };
  }
</loader>

Production Setup

Deploy PureMix applications to production with simple commands. No build step required - Node.js 22+ has native TypeScript support.

Basic Production Start

# Set environment and start server
NODE_ENV=production puremix start --port 3000 --host 0.0.0.0

Environment Variables

# Create .env.production
NODE_ENV=production
PORT=3000
HOST=0.0.0.0
DATABASE_URL=postgresql://user:pass@localhost:5432/mydb
PYTHON_WORKERS=4
VERBOSE_DEBUG=false

Health Checks

// File: app/routes/health.js
export default async function handler(request, response) {
  const status = {
    status: 'ok',
    timestamp: new Date().toISOString(),
    uptime: process.uptime(),
    python: pythonExecutor.available(),
    workers: pythonPool.getWorkerCount()
  };

  return response.json(status);
}

PM2 Process Manager

Use PM2 for production process management, including cluster mode, auto-restart, and monitoring.

Basic PM2 Usage

# Install PM2
npm install -g pm2

# Start application
pm2 start "puremix start" --name my-app

# Check status
pm2 status

# View logs
pm2 logs my-app

# Restart
pm2 restart my-app

# Stop
pm2 stop my-app

Cluster Mode

# Run 4 instances in cluster mode
pm2 start "puremix start" \
  --name my-app \
  --instances 4 \
  --exec-mode cluster

# Auto-scale based on CPU cores
pm2 start "puremix start" \
  --name my-app \
  --instances max \
  --exec-mode cluster

PM2 Ecosystem File

// ecosystem.config.js
module.exports = {
  apps: [{
    name: 'puremix-app',
    script: 'puremix',
    args: 'start',
    instances: 4,
    exec_mode: 'cluster',
    env: {
      NODE_ENV: 'production',
      PORT: 3000,
      HOST: '0.0.0.0'
    },
    error_file: './logs/err.log',
    out_file: './logs/out.log',
    log_date_format: 'YYYY-MM-DD HH:mm:ss Z',
    merge_logs: true
  }]
};

// Start with ecosystem file
pm2 start ecosystem.config.js

Process Management

# Save PM2 process list
pm2 save

# Restore processes on boot
pm2 startup

# Monitor processes
pm2 monit

# Show process info
pm2 show my-app

# Delete process
pm2 delete my-app

Troubleshooting

Common issues and solutions when working with PureMix:

Template Expressions Not Working

❌ Problem: {user.name} shows as literal string "{user.name}"

✅ Solution:

  • Check if inside script tags: Template expressions don't work in <script> tags
  • Use PureMix.data instead: const userData = PureMix.data.loadPage?.data?.user;
  • Verify loader is running: Check console for loader execution logs
// ❌ Won't work in script tags
const name = '{user.name}';

// ✅ Correct approach
const userData = PureMix.data.loadPage?.data?.user;
const name = userData?.name || 'Guest';

Component Props Not Updating

❌ Problem: Props changes don't reflect in component

✅ Solution:

  • Check loader signature: Ensure third parameter is named props
  • Verify props usage: Access via props?.user not direct user
  • Return props in data: Must return props in loader's data object
// ✅ Correct component loader
async function loadComponent(request, actionResult, props) {
  const user = props?.user || defaultUser;
  return { data: { user } };
}

Python Function Not Found

❌ Problem: "Python function not available" error

✅ Solution:

  • Check server startup logs: Look for "Python modules: X/X modules" message
  • Verify file location: Python files must be in app/ directory
  • Check function name: Ensure function is defined with def
  • Restart server: Python files are scanned at startup
# Check if Python is available
python3 --version

# Restart development server to rescan
npm run dev

Server Function 404 Errors

❌ Problem: Server function calls return 404

✅ Solution:

  • Check function name: Must match onsubmit or PureMix.call() name
  • Verify script tag: Function must be in <script server> block
  • Component namespacing: Use ComponentName.functionName for component functions
  • Check console: Look for function registration messages

Loader Data Undefined

❌ Problem: loadPage.data.user is undefined

✅ Solution:

  • Use optional chaining: {loadPage?.data?.user?.name}
  • Provide fallbacks: {loadPage?.data?.user?.name || 'Guest'}
  • Check loader return: Must return object with data property
  • Verify async/await: Ensure loader waits for async operations

Hot Reload Not Working

❌ Problem: Changes don't trigger server restart

✅ Solution:

  • Check file location: Only app/ directory is watched
  • Verify npm script: Ensure using npm run dev
  • Check ignored files: node_modules/, .git/ are ignored
  • Manual restart: Press Ctrl+C and run npm run dev again

TypeScript Compilation Errors

❌ Problem: TypeScript errors in .puremix files

✅ Solution:

  • Run type check: npx tsc --noEmit
  • Check imports: Verify import paths and file extensions
  • Node.js version: Ensure Node.js 22+ for native TypeScript support
  • Review errors: Fix reported type mismatches

Performance Issues

❌ Problem: Slow page loads or server responses

✅ Solution:

  • Optimize loaders: Reduce database queries and async operations
  • Check Python pools: Ensure 4 workers are running (check startup logs)
  • Enable caching: Cache expensive computations
  • Profile code: Use console.time() to find bottlenecks
  • Review logs: Set VERBOSE_DEBUG=true for detailed timing

Getting More Help

  • Enable verbose logging: VERBOSE_DEBUG=true npm run dev
  • Check GitHub issues: Report bugs or search existing issues
  • Review test examples: See tests/projects/comprehensive-test/ for working patterns
  • Read CLAUDE.md: Comprehensive framework documentation in repository

Docker Deployment

Deploy PureMix applications using Docker for consistent environments.

Dockerfile

# Dockerfile
FROM node:22-alpine

# Install Python (optional, for Python integration)
RUN apk add --no-cache python3 py3-pip

# Set working directory
WORKDIR /app

# Copy package files
COPY package*.json ./

# Install dependencies
RUN npm ci --only=production

# Install PureMix
RUN npm install -g puremix@alpha

# Copy application files
COPY . .

# Expose port
EXPOSE 3000

# Set environment
ENV NODE_ENV=production
ENV PORT=3000
ENV HOST=0.0.0.0

# Start application
CMD ["puremix", "start"]

Docker Compose

# docker-compose.yml
version: '3.8'

services:
  app:
    build: .
    ports:
      - "3000:3000"
    environment:
      NODE_ENV: production
      PORT: 3000
      HOST: 0.0.0.0
      DATABASE_URL: postgresql://user:pass@db:5432/mydb
    depends_on:
      - db
    restart: unless-stopped

  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_USER: user
      POSTGRES_PASSWORD: pass
      POSTGRES_DB: mydb
    volumes:
      - postgres_data:/var/lib/postgresql/data
    restart: unless-stopped

volumes:
  postgres_data:

Build and Run

# Build image
docker build -t my-puremix-app .

# Run container
docker run -p 3000:3000 my-puremix-app

# Docker Compose
docker-compose up -d

# View logs
docker-compose logs -f app

Nginx Configuration

Use Nginx as a reverse proxy for PureMix applications with SSL, load balancing, and caching.

Basic Reverse Proxy

# /etc/nginx/sites-available/puremix-app
server {
    listen 80;
    server_name example.com www.example.com;

    location / {
        proxy_pass http://localhost:3000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_cache_bypass $http_upgrade;
    }

    # Static files
    location /public/ {
        alias /var/www/puremix-app/public/;
        expires 30d;
        add_header Cache-Control "public, immutable";
    }
}

SSL with Let's Encrypt

server {
    listen 443 ssl http2;
    server_name example.com www.example.com;

    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers HIGH:!aNULL:!MD5;

    location / {
        proxy_pass http://localhost:3000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_cache_bypass $http_upgrade;
    }
}

# Redirect HTTP to HTTPS
server {
    listen 80;
    server_name example.com www.example.com;
    return 301 https://$server_name$request_uri;
}

Load Balancing

upstream puremix_backend {
    least_conn;
    server localhost:3000;
    server localhost:3001;
    server localhost:3002;
    server localhost:3003;
}

server {
    listen 80;
    server_name example.com;

    location / {
        proxy_pass http://puremix_backend;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_cache_bypass $http_upgrade;
    }
}

Enable Configuration

# Enable site
sudo ln -s /etc/nginx/sites-available/puremix-app /etc/nginx/sites-enabled/

# Test configuration
sudo nginx -t

# Reload Nginx
sudo systemctl reload nginx

# Install SSL certificate with Certbot
sudo certbot --nginx -d example.com -d www.example.com