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.
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:
- HTTP Request → Express receives the request (GET, POST, etc.)
- Route Resolution → PureMix maps URL to .puremix file using file-based routing
- Action Execution → If POST, server functions execute with form data
- Loader Execution → Data fetching function runs (receives action results if POST)
- Template Rendering → PureMix engine processes HTML with AST-based template interpreter
- Client Runtime → Injects browser-side PureMix API for AJAX and component updates
- 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.
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 parametersrequest.method- HTTP method (GET, POST, etc.)request.headers- HTTP headersrequest.cookies- Parsed cookiesrequest.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.
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>
}
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
{...} 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);
}
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?.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)
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
.pyfiles 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_functionfallback - 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:
export default async function handler(req, res)export async function GET(req, res)(or POST, PUT, DELETE)export async function get(req, res)(lowercase)export async function handle/process/execute(req, res)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 AJAXPureMix.data- Access loader results in client scriptsPureMix.data.{loaderName}.data- Loader data objectPureMix.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
{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
✅ Solution:
- Check loader signature: Ensure third parameter is named
props - Verify props usage: Access via
props?.usernot directuser - 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
✅ 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
✅ Solution:
- Check function name: Must match
onsubmitorPureMix.call()name - Verify script tag: Function must be in
<script server>block - Component namespacing: Use
ComponentName.functionNamefor component functions - Check console: Look for function registration messages
Loader Data Undefined
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
dataproperty - Verify async/await: Ensure loader waits for async operations
Hot Reload Not Working
✅ 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 devagain
TypeScript Compilation Errors
.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
✅ 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=truefor 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