Deploying Expressa
Introduction
Expressa is a middleware library for building powerful backends and APIs rapidly. It provides an automatic admin interface, JSON:API compliant REST endpoints, and flexible data modeling—all without writing boilerplate code. Think of it as an instant backend that turns your database into a full-featured API with a visual admin panel for managing data.
Built on top of Express.js, Expressa allows developers to define collections through simple JSON schemas and immediately get working CRUD endpoints, role-based permissions, data validation, and a user-friendly interface for managing content. Whether you’re building a mobile app backend, a content management system, or prototyping a new product, Expressa accelerates development by eliminating repetitive API code.
Key Features
Expressa empowers rapid development with:
- Instant Admin Interface: Beautiful, automatically generated admin panel for managing data
- JSON:API Compliant: RESTful endpoints following the JSON:API specification
- Schema-Based Collections: Define data models with JSON Schema for automatic validation
- Role-Based Permissions: Granular access control for users, collections, and individual documents
- File Uploads: Built-in support for file uploads with configurable storage backends
- MongoDB Backend: Flexible NoSQL storage for dynamic data structures
- Authentication: JWT-based authentication with user management built-in
- Real-Time Queries: Support for complex queries with filtering, sorting, and pagination
- Relationships: Define relationships between collections (one-to-one, one-to-many, many-to-many)
- Custom Endpoints: Extend with custom Express routes and middleware
- Email Integration: Built-in email functionality for notifications and user registration
- Data Export: Export collection data to CSV or JSON formats
- Audit Logging: Track all changes to data with automatic audit trails
- Extensible: Plugin architecture for adding custom functionality
- TypeScript Support: Full TypeScript definitions for type-safe development
Why Deploy Expressa on Klutch.sh?
Deploying Expressa on Klutch.sh provides significant advantages:
- Rapid Deployment: Deploy from GitHub with automatic Docker detection
- Persistent Storage: Built-in volume support for uploaded files and configuration
- Database Integration: Easy MongoDB setup with TCP connectivity
- HTTPS by Default: Automatic SSL certificates for secure API access
- Environment Management: Secure configuration of secrets and API keys
- Scalable Resources: Adjust compute resources as your API usage grows
- High Availability: Reliable infrastructure ensures 24/7 API availability
- Custom Domains: Use your own domain for professional API endpoints
- Zero Downtime Deploys: Rolling updates keep your API online during deploys
Prerequisites
Before deploying Expressa, ensure you have:
- A Klutch.sh account
- A GitHub account with a repository for your deployment configuration
- A MongoDB database (see our MongoDB deployment guide)
- Basic familiarity with Docker, REST APIs, and JSON Schema
- (Optional) SMTP credentials for email notifications
Preparing Your Repository
Create a new GitHub repository for your Expressa deployment with the following structure:
expressa-deploy/├── Dockerfile├── package.json├── server.js├── collections/│ └── .gitkeep├── data/│ └── .gitkeep├── .env.example├── .dockerignore├── README.md└── .gitignoreDockerfile
Create a production-ready Dockerfile for Expressa:
FROM node:20-alpine
# Install system dependenciesRUN apk add --no-cache \ tini \ curl \ ca-certificates
# Create app user for securityRUN addgroup -g 1001 -S nodejs && \ adduser -S nodejs -u 1001
# Set working directoryWORKDIR /app
# Copy package filesCOPY package*.json ./
# Install dependenciesRUN npm ci --only=production && \ npm cache clean --force
# Copy application filesCOPY --chown=nodejs:nodejs . .
# Create directories for data and uploadsRUN mkdir -p /app/data /app/uploads /app/collections && \ chown -R nodejs:nodejs /app/data /app/uploads /app/collections
# Switch to non-root userUSER nodejs
# Expose application portEXPOSE 3000
# Health checkHEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \ CMD curl -f http://localhost:3000/health || exit 1
# Use tini for proper signal handlingENTRYPOINT ["/sbin/tini", "--"]
# Start the applicationCMD ["node", "server.js"]Package.json
Create a package.json file with Expressa dependencies:
{ "name": "expressa-app", "version": "1.0.0", "description": "Expressa API backend on Klutch.sh", "main": "server.js", "scripts": { "start": "node server.js", "dev": "nodemon server.js" }, "keywords": ["expressa", "api", "admin", "backend"], "author": "Your Name", "license": "MIT", "dependencies": { "expressa": "^0.3.13", "express": "^4.18.2", "mongodb": "^6.3.0", "dotenv": "^16.3.1", "cors": "^2.8.5", "helmet": "^7.1.0", "compression": "^1.7.4", "morgan": "^1.10.0" }, "devDependencies": { "nodemon": "^3.0.2" }, "engines": { "node": ">=18.0.0" }}Server.js
Create the main server.js file:
require('dotenv').config();const express = require('express');const expressa = require('expressa');const cors = require('cors');const helmet = require('helmet');const compression = require('compression');const morgan = require('morgan');
const app = express();const PORT = process.env.PORT || 3000;
// Security middlewareapp.use(helmet({ contentSecurityPolicy: false, crossOriginEmbedderPolicy: false}));
// CORS configurationapp.use(cors({ origin: process.env.CORS_ORIGIN || '*', credentials: true}));
// Compression middlewareapp.use(compression());
// Loggingapp.use(morgan(process.env.NODE_ENV === 'production' ? 'combined' : 'dev'));
// Health check endpointapp.get('/health', (req, res) => { res.json({ status: 'healthy', timestamp: new Date().toISOString(), uptime: process.uptime() });});
// Initialize Expressaconst expressaSettings = { // Database connection mongodb_uri: process.env.MONGODB_URI || 'mongodb://localhost:27017/expressa',
// Storage paths collection_path: process.env.COLLECTION_PATH || './collections', data_path: process.env.DATA_PATH || './data',
// File uploads file_storage: { type: process.env.FILE_STORAGE_TYPE || 'local', path: process.env.FILE_STORAGE_PATH || './uploads' },
// Authentication jwt_secret: process.env.JWT_SECRET || 'change-this-secret-in-production',
// Email configuration (optional) email: process.env.SMTP_HOST ? { host: process.env.SMTP_HOST, port: parseInt(process.env.SMTP_PORT) || 587, secure: process.env.SMTP_SECURE === 'true', auth: { user: process.env.SMTP_USER, pass: process.env.SMTP_PASSWORD }, from: process.env.SMTP_FROM || 'noreply@example.com' } : undefined,
// Admin configuration admin_user: { email: process.env.ADMIN_EMAIL || 'admin@example.com', password: process.env.ADMIN_PASSWORD || 'admin123' },
// API configuration api_prefix: process.env.API_PREFIX || '/api',
// Enable features enforce_permissions: process.env.ENFORCE_PERMISSIONS !== 'false',
// Development mode dev: process.env.NODE_ENV !== 'production'};
// Initialize Expressa middlewareexpressa.addMiddleware(app, expressaSettings);
// Custom routes can be added hereapp.get('/', (req, res) => { res.json({ message: 'Expressa API is running', admin: '/admin', api: expressaSettings.api_prefix, docs: '/api-docs' });});
// Error handling middlewareapp.use((err, req, res, next) => { console.error('Error:', err); res.status(err.status || 500).json({ error: { message: err.message || 'Internal server error', status: err.status || 500 } });});
// Start serverapp.listen(PORT, () => { console.log(`Expressa server running on port ${PORT}`); console.log(`Admin interface: http://localhost:${PORT}/admin`); console.log(`API endpoint: http://localhost:${PORT}${expressaSettings.api_prefix}`);});
// Graceful shutdownprocess.on('SIGTERM', () => { console.log('SIGTERM signal received: closing HTTP server'); app.close(() => { console.log('HTTP server closed'); });});Environment Variables Template
Create a .env.example file:
# Application ConfigurationNODE_ENV=productionPORT=3000API_PREFIX=/api
# MongoDB Connection (Required)MONGODB_URI=mongodb://admin:password@your-mongodb-app.klutch.sh:8000/expressa?authSource=admin
# Security (Required)JWT_SECRET=generate_with_openssl_rand_base64_64ADMIN_EMAIL=admin@example.comADMIN_PASSWORD=change_this_secure_password
# Storage PathsCOLLECTION_PATH=/app/collectionsDATA_PATH=/app/dataFILE_STORAGE_TYPE=localFILE_STORAGE_PATH=/app/uploads
# CORS ConfigurationCORS_ORIGIN=*
# PermissionsENFORCE_PERMISSIONS=true
# Email Configuration (Optional)SMTP_HOST=smtp.gmail.comSMTP_PORT=587SMTP_SECURE=falseSMTP_USER=your-email@gmail.comSMTP_PASSWORD=your_app_passwordSMTP_FROM=Expressa <noreply@example.com>
# Feature FlagsENABLE_FILE_UPLOADS=trueENABLE_EMAIL_NOTIFICATIONS=trueMAX_UPLOAD_SIZE=10485760
# Rate LimitingRATE_LIMIT_WINDOW_MS=900000RATE_LIMIT_MAX_REQUESTS=100Docker Ignore File
Create a .dockerignore file:
.git.gitignore.env.env.**.mdREADME.mddocker-compose.yml.vscode.idea*.log.DS_Storenode_modulescoverage.nyc_output*.test.js*.spec.jstest/tests/__tests__uploads/*!uploads/.gitkeepdata/*!data/.gitkeepcollections/*!collections/.gitkeepGit Ignore File
Create a .gitignore file:
.env.env.local.env.production*.log.DS_Store.vscode/.idea/node_modules/coverage/.nyc_output/uploads/*!uploads/.gitkeepdata/*!data/.gitkeepcollections/*.json!collections/.gitkeepInitialize Directory Structure
Create placeholder files for directories:
mkdir -p collections data uploadstouch collections/.gitkeep data/.gitkeep uploads/.gitkeepLocal Development (Optional)
Test your Expressa setup locally before deploying:
Using Docker Locally
# Build the Docker imagedocker build -t expressa-app .
# Run with environment variablesdocker run -d \ --name expressa-test \ -p 3000:3000 \ -e MONGODB_URI=mongodb://localhost:27017/expressa \ -e JWT_SECRET=test_secret \ -e ADMIN_EMAIL=admin@example.com \ -e ADMIN_PASSWORD=admin123 \ -v $(pwd)/uploads:/app/uploads \ -v $(pwd)/data:/app/data \ expressa-app
# View logsdocker logs -f expressa-test
# Stop and removedocker stop expressa-testdocker rm expressa-testUsing Node.js Directly
# Install dependenciesnpm install
# Create .env filecp .env.example .env# Edit .env with your local MongoDB connection
# Start development servernpm run devAccess the admin interface at http://localhost:3000/admin and log in with your admin credentials.
Deploying on Klutch.sh
-
Set Up MongoDB Database
Before deploying Expressa, you need a MongoDB database. Follow our MongoDB deployment guide to set up a database on Klutch.sh.
When creating your MongoDB instance, configure:
- Database name:
expressa - Username:
expressa - Password: Use a strong, randomly generated password
- Connection details: Note your app URL (e.g.,
your-mongodb-app.klutch.sh:8000)
Your MongoDB connection string will be:
mongodb://expressa:your_password@your-mongodb-app.klutch.sh:8000/expressa?authSource=admin - Database name:
-
Generate Security Keys
Generate a strong JWT secret for authentication:
Terminal window # Generate JWT secret (64 bytes)openssl rand -base64 64Save this value securely—you’ll need it for your Klutch.sh environment variables.
-
Push to GitHub
Commit your Expressa configuration to GitHub:
Terminal window git add .git commit -m "Initial Expressa setup for Klutch.sh"git remote add origin https://github.com/yourusername/expressa-deploy.gitgit push -u origin main -
Create a New Project
Log in to the Klutch.sh dashboard and create a new project for your Expressa deployment.
-
Connect Your GitHub Repository
In your Klutch.sh project:
- Navigate to the Apps section
- Click “Create New App”
- Select GitHub as your source
- Choose the repository containing your Expressa Dockerfile
- Select the branch you want to deploy (typically
mainormaster)
-
Configure Build Settings
Klutch.sh will automatically detect your Dockerfile in the repository root. No additional build configuration is needed.
-
Set Traffic Type
In the deployment settings:
- Select HTTP as the traffic type
- Set the internal port to 3000
- Klutch.sh will automatically route traffic to your Expressa application
-
Configure Environment Variables
In your Klutch.sh app settings, add the following environment variables:
Required Variables:
Terminal window NODE_ENV=productionPORT=3000MONGODB_URI=mongodb://expressa:your_password@your-mongodb-app.klutch.sh:8000/expressa?authSource=adminJWT_SECRET=your_generated_jwt_secret_64_bytesADMIN_EMAIL=admin@example.comADMIN_PASSWORD=your_admin_passwordOptional Variables (Email):
Terminal window SMTP_HOST=smtp.gmail.comSMTP_PORT=587SMTP_USER=your-email@gmail.comSMTP_PASSWORD=your_app_passwordSMTP_FROM=Expressa <noreply@example.com>Optional Variables (Configuration):
Terminal window API_PREFIX=/apiCORS_ORIGIN=https://yourfrontend.comENFORCE_PERMISSIONS=trueENABLE_FILE_UPLOADS=trueMAX_UPLOAD_SIZE=10485760Mark all sensitive values like
MONGODB_URI,JWT_SECRET,ADMIN_PASSWORD, andSMTP_PASSWORDas secrets in the Klutch.sh dashboard. -
Attach Persistent Volumes
Expressa needs persistent storage for uploaded files and collection definitions:
Upload Volume:
- In your Klutch.sh app settings, navigate to Volumes
- Click “Add Volume”
- Set the mount path to
/app/uploads - Choose an appropriate size (start with 5GB for file storage)
- Save the volume configuration
Data Volume:
- Click “Add Volume” again
- Set the mount path to
/app/data - Choose an appropriate size (start with 2GB for application data)
- Save the volume configuration
Collections Volume:
- Click “Add Volume” again
- Set the mount path to
/app/collections - Choose an appropriate size (start with 1GB for schema definitions)
- Save the volume configuration
-
Deploy the Application
Click “Deploy” to start the deployment process. Klutch.sh will:
- Pull your code from GitHub
- Build the Docker image using the Dockerfile
- Configure networking and volumes
- Start your Expressa server
- Initialize the admin user on first run
The initial deployment typically takes 2-4 minutes.
-
Verify Deployment
Once deployed, verify your Expressa instance:
Terminal window # Check health endpointcurl https://example-app.klutch.sh/health# Expected response{"status": "healthy","timestamp": "2025-12-19T...","uptime": 123}Access the admin interface at
https://example-app.klutch.sh/adminand log in with your admin credentials.
Getting Started with Expressa
Accessing the Admin Interface
- Navigate to
https://example-app.klutch.sh/admin - Log in with your admin credentials (from
ADMIN_EMAILandADMIN_PASSWORD) - You’ll see the Expressa dashboard with the default collections
Creating Your First Collection
- Click “Collections” in the admin sidebar
- Click “Create Collection”
- Define your collection schema:
- Click “Save”
- The collection and its API endpoints are now available
{ "name": "blog_posts", "schema": { "type": "object", "properties": { "title": { "type": "string", "minLength": 1, "maxLength": 200 }, "slug": { "type": "string", "pattern": "^[a-z0-9-]+$" }, "content": { "type": "string" }, "author": { "type": "string" }, "published": { "type": "boolean", "default": false }, "tags": { "type": "array", "items": { "type": "string" } }, "created_at": { "type": "string", "format": "date-time" } }, "required": ["title", "content", "author"] }}Adding Data
- Navigate to your collection in the admin interface
- Click “Add Item”
- Fill in the form fields based on your schema
- Click “Save”
Configuring Permissions
- Click on your collection
- Click “Permissions”
- Set role-based access:
"*"means public access- Named roles require authentication
{ "create": ["Admin", "Author"], "read": ["*"], "update": ["Admin", "Author"], "delete": ["Admin"]}Using the Expressa API
Authentication
Register a New User:
curl -X POST https://example-app.klutch.sh/api/user \ -H "Content-Type: application/json" \ -d '{ "email": "user@example.com", "password": "securepassword", "role": "User" }'Login:
curl -X POST https://example-app.klutch.sh/api/user/login \ -H "Content-Type: application/json" \ -d '{ "email": "user@example.com", "password": "securepassword" }'
# Response includes JWT token{ "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", "user": { ... }}CRUD Operations
Create (POST):
curl -X POST https://example-app.klutch.sh/api/blog_posts \ -H "Content-Type: application/json" \ -H "Authorization: Bearer YOUR_JWT_TOKEN" \ -d '{ "title": "Getting Started with Expressa", "slug": "getting-started", "content": "Expressa makes API development fast...", "author": "John Doe", "published": true, "tags": ["tutorial", "api"], "created_at": "2025-12-19T10:00:00Z" }'Read (GET):
# Get all itemscurl https://example-app.klutch.sh/api/blog_posts
# Get single item by IDcurl https://example-app.klutch.sh/api/blog_posts/507f1f77bcf86cd799439011
# Filter with query parameterscurl "https://example-app.klutch.sh/api/blog_posts?filter[published]=true&sort=-created_at&limit=10"
# Searchcurl "https://example-app.klutch.sh/api/blog_posts?filter[title][\$regex]=Expressa"Update (PUT):
curl -X PUT https://example-app.klutch.sh/api/blog_posts/507f1f77bcf86cd799439011 \ -H "Content-Type: application/json" \ -H "Authorization: Bearer YOUR_JWT_TOKEN" \ -d '{ "title": "Updated Title", "published": true }'Delete (DELETE):
curl -X DELETE https://example-app.klutch.sh/api/blog_posts/507f1f77bcf86cd799439011 \ -H "Authorization: Bearer YOUR_JWT_TOKEN"Sample Code for Common Tasks
Node.js Client
const fetch = require('node-fetch');
const API_URL = 'https://example-app.klutch.sh/api';let authToken = null;
async function login(email, password) { const response = await fetch(`${API_URL}/user/login`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email, password }) });
const data = await response.json(); authToken = data.token; return data;}
async function createPost(postData) { const response = await fetch(`${API_URL}/blog_posts`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${authToken}` }, body: JSON.stringify(postData) });
return await response.json();}
async function getPosts(filters = {}) { const params = new URLSearchParams(filters); const response = await fetch(`${API_URL}/blog_posts?${params}`); return await response.json();}
async function updatePost(id, updates) { const response = await fetch(`${API_URL}/blog_posts/${id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${authToken}` }, body: JSON.stringify(updates) });
return await response.json();}
async function deletePost(id) { const response = await fetch(`${API_URL}/blog_posts/${id}`, { method: 'DELETE', headers: { 'Authorization': `Bearer ${authToken}` } });
return response.ok;}
// Usage(async () => { await login('user@example.com', 'password');
const post = await createPost({ title: 'My First Post', content: 'Hello Expressa!', author: 'User Name', published: true });
console.log('Created post:', post);
const posts = await getPosts({ 'filter[published]': true }); console.log('All published posts:', posts);})();Python Client
import requestsfrom typing import Dict, List, Optional
class ExpressaClient: def __init__(self, base_url: str): self.base_url = base_url.rstrip('/') self.api_url = f"{self.base_url}/api" self.token: Optional[str] = None
def login(self, email: str, password: str) -> Dict: """Authenticate and get JWT token""" response = requests.post( f"{self.api_url}/user/login", json={"email": email, "password": password} ) response.raise_for_status() data = response.json() self.token = data['token'] return data
def _headers(self) -> Dict[str, str]: """Get headers with authentication""" headers = {"Content-Type": "application/json"} if self.token: headers["Authorization"] = f"Bearer {self.token}" return headers
def create(self, collection: str, data: Dict) -> Dict: """Create a new item in a collection""" response = requests.post( f"{self.api_url}/{collection}", json=data, headers=self._headers() ) response.raise_for_status() return response.json()
def read(self, collection: str, item_id: Optional[str] = None, filters: Optional[Dict] = None) -> Dict: """Read items from a collection""" url = f"{self.api_url}/{collection}" if item_id: url += f"/{item_id}"
response = requests.get(url, params=filters or {}) response.raise_for_status() return response.json()
def update(self, collection: str, item_id: str, data: Dict) -> Dict: """Update an item in a collection""" response = requests.put( f"{self.api_url}/{collection}/{item_id}", json=data, headers=self._headers() ) response.raise_for_status() return response.json()
def delete(self, collection: str, item_id: str) -> bool: """Delete an item from a collection""" response = requests.delete( f"{self.api_url}/{collection}/{item_id}", headers=self._headers() ) return response.ok
# Usageclient = ExpressaClient("https://example-app.klutch.sh")client.login("user@example.com", "password")
# Create a postpost = client.create("blog_posts", { "title": "Python Integration", "content": "Using Expressa with Python", "author": "Python Developer", "published": True})print(f"Created post: {post['_id']}")
# Get all published postsposts = client.read("blog_posts", filters={"filter[published]": "true"})print(f"Found {len(posts)} posts")
# Update a postupdated = client.update("blog_posts", post['_id'], { "title": "Updated Python Integration"})
# Delete a post# client.delete("blog_posts", post['_id'])React Frontend Example
import React, { useState, useEffect } from 'react';
const API_URL = 'https://example-app.klutch.sh/api';
function BlogApp() { const [posts, setPosts] = useState([]); const [token, setToken] = useState(localStorage.getItem('token'));
useEffect(() => { fetchPosts(); }, []);
const fetchPosts = async () => { try { const response = await fetch(`${API_URL}/blog_posts?filter[published]=true&sort=-created_at`); const data = await response.json(); setPosts(data); } catch (error) { console.error('Error fetching posts:', error); } };
const login = async (email, password) => { try { const response = await fetch(`${API_URL}/user/login`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email, password }) }); const data = await response.json(); setToken(data.token); localStorage.setItem('token', data.token); } catch (error) { console.error('Login error:', error); } };
const createPost = async (postData) => { try { const response = await fetch(`${API_URL}/blog_posts`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` }, body: JSON.stringify(postData) }); const newPost = await response.json(); setPosts([newPost, ...posts]); } catch (error) { console.error('Error creating post:', error); } };
return ( <div> <h1>Blog Posts</h1> {posts.map(post => ( <article key={post._id}> <h2>{post.title}</h2> <p>{post.content}</p> <small>By {post.author}</small> </article> ))} </div> );}
export default BlogApp;Advanced Configuration
Custom Domain Setup
Use your own domain for your Expressa API:
-
Add Custom Domain in Klutch.sh:
- Navigate to your app in the Klutch.sh dashboard
- Go to Domains section
- Click “Add Custom Domain”
- Enter your domain (e.g.,
api.example.com)
-
Configure DNS:
-
Add a CNAME record in your DNS provider:
Type: CNAMEName: apiValue: example-app.klutch.shTTL: 3600
-
-
Update CORS Configuration:
Terminal window CORS_ORIGIN=https://yourfrontend.com -
Redeploy:
- Redeploy your application for changes to take effect
- Wait for DNS propagation (typically 5-30 minutes)
File Upload Configuration
Configure file uploads for user-generated content:
Local Storage (Default):
FILE_STORAGE_TYPE=localFILE_STORAGE_PATH=/app/uploadsMAX_UPLOAD_SIZE=10485760Collection Schema with File Field:
{ "name": "products", "schema": { "properties": { "name": { "type": "string" }, "image": { "type": "string", "format": "file" } } }}Upload File via API:
curl -X POST https://example-app.klutch.sh/api/file \ -H "Authorization: Bearer YOUR_TOKEN" \ -F "file=@product-image.jpg"
# Response{ "filename": "abc123.jpg", "url": "/api/file/abc123.jpg"}Email Notification Setup
Configure email for user registration and notifications:
Gmail Configuration:
SMTP_HOST=smtp.gmail.comSMTP_PORT=587SMTP_SECURE=falseSMTP_USER=your-email@gmail.comSMTP_PASSWORD=your-app-specific-passwordSMTP_FROM=Expressa <noreply@example.com>SendGrid Configuration:
SMTP_HOST=smtp.sendgrid.netSMTP_PORT=587SMTP_USER=apikeySMTP_PASSWORD=your-sendgrid-api-keySMTP_FROM=Expressa <noreply@example.com>Database Backup Strategy
Set up automated MongoDB backups:
#!/bin/bashDATE=$(date +%Y%m%d_%H%M%S)BACKUP_DIR="/backups"MONGODB_URI="mongodb://expressa:password@your-mongodb-app.klutch.sh:8000/expressa?authSource=admin"
# Backup databasemongodump --uri="${MONGODB_URI}" --out="${BACKUP_DIR}/backup_${DATE}"
# Compress backuptar -czf "${BACKUP_DIR}/backup_${DATE}.tar.gz" "${BACKUP_DIR}/backup_${DATE}"rm -rf "${BACKUP_DIR}/backup_${DATE}"
# Upload to S3 or backup service# aws s3 cp "${BACKUP_DIR}/backup_${DATE}.tar.gz" s3://your-bucket/expressa/
# Clean up old backups (keep last 30 days)find "${BACKUP_DIR}" -name "backup_*.tar.gz" -mtime +30 -delete
echo "Backup completed: backup_${DATE}.tar.gz"Production Best Practices
Security Hardening
Environment Variables:
- Never commit
.envfiles to version control - Use Klutch.sh’s secret management for all sensitive values
- Rotate JWT secret quarterly
- Use strong admin passwords (minimum 16 characters)
Generate Secure Secrets:
# JWT secretopenssl rand -base64 64
# Admin passwordopenssl rand -base64 32API Security:
- Enable CORS only for trusted origins
- Use HTTPS only (handled by Klutch.sh)
- Implement rate limiting for public endpoints
- Validate all input data with JSON Schema
- Use JWT tokens with reasonable expiration times
- Enable permission enforcement in production
Performance Optimization
Database Indexing:
// In MongoDB shell or through Expressa hooksdb.blog_posts.createIndex({ published: 1, created_at: -1 });db.blog_posts.createIndex({ author: 1 });db.blog_posts.createIndex({ slug: 1 }, { unique: true });db.blog_posts.createIndex({ tags: 1 });Application Caching:
Consider adding Redis caching for frequently accessed data:
const redis = require('redis');const client = redis.createClient({ url: process.env.REDIS_URL});
// Cache middlewareapp.get('/api/blog_posts', async (req, res, next) => { const cacheKey = `posts:${JSON.stringify(req.query)}`; const cached = await client.get(cacheKey);
if (cached) { return res.json(JSON.parse(cached)); }
// Continue to Expressa handler next();});Response Compression:
Enabled by default in our server.js with the compression middleware.
Monitoring and Logging
Health Monitoring:
#!/bin/bashHEALTH_URL="https://example-app.klutch.sh/health"
check_health() { response=$(curl -s -o /dev/null -w "%{http_code}" "$HEALTH_URL")
if [ "$response" != "200" ]; then echo "Expressa health check failed: HTTP $response" # Send alert return 1 fi
echo "Expressa is healthy" return 0}
check_healthKey Metrics to Monitor:
- API response times
- Database connection pool usage
- Error rates by endpoint
- Upload storage usage
- Memory and CPU utilization
- Active user sessions
- API request rate
Log Aggregation:
Access logs through Klutch.sh dashboard or set up centralized logging:
// Add structured loggingconst winston = require('winston');
const logger = winston.createLogger({ level: 'info', format: winston.format.json(), transports: [ new winston.transports.File({ filename: 'error.log', level: 'error' }), new winston.transports.File({ filename: 'combined.log' }) ]});
// Log API requestsapp.use((req, res, next) => { logger.info('API Request', { method: req.method, path: req.path, user: req.user?.email }); next();});Scaling Considerations
Vertical Scaling:
- Small (< 1000 requests/day): 512MB RAM, 0.5 CPU
- Medium (1000-10000 requests/day): 1GB RAM, 1 CPU
- Large (10000-100000 requests/day): 2GB RAM, 2 CPU
- Enterprise (> 100000 requests/day): 4GB RAM, 4+ CPU
Horizontal Scaling:
- Deploy multiple instances behind a load balancer
- Use sticky sessions for file uploads
- Share MongoDB connection across instances
- Consider read replicas for heavy read workloads
Storage Scaling:
- Monitor volume usage regularly
- Scale upload volume as user content grows
- Consider S3 for long-term file storage
- Implement file cleanup policies for temporary uploads
Troubleshooting
Application Won’t Start
Check MongoDB Connection:
# Test MongoDB connectivitymongosh "mongodb://expressa:password@your-mongodb-app.klutch.sh:8000/expressa?authSource=admin"Verify Environment Variables:
- Ensure
MONGODB_URIis correctly formatted - Check that
JWT_SECRETis set - Verify admin credentials are configured
- Confirm PORT is set to 3000
Check Logs:
Review deployment logs in Klutch.sh dashboard for:
- MongoDB connection errors
- Missing environment variables
- Port binding issues
- Node.js runtime errors
Cannot Access Admin Interface
Common Issues:
-
Wrong URL:
- Admin interface is at
/adminnot/ - Correct URL:
https://example-app.klutch.sh/admin
- Admin interface is at
-
Invalid Credentials:
- Verify
ADMIN_EMAILandADMIN_PASSWORDin environment variables - Check for typos in credentials
- Admin user is created on first startup
- Verify
-
CORS Issues:
- If accessing from different domain, configure
CORS_ORIGIN - Check browser console for CORS errors
- If accessing from different domain, configure
API Requests Failing
Authentication Errors:
# Verify JWT token is validcurl https://example-app.klutch.sh/api/user/me \ -H "Authorization: Bearer YOUR_TOKEN"Permission Errors:
- Check collection permissions in admin interface
- Verify user role matches required permissions
- Review
ENFORCE_PERMISSIONSsetting
Validation Errors:
- Ensure request body matches collection schema
- Check required fields are present
- Verify data types match schema definitions
File Upload Issues
Check Volume Mount:
Ensure /app/uploads volume is properly mounted:
# Inside container (for debugging)ls -la /app/uploads# Should be writableCheck File Size Limits:
MAX_UPLOAD_SIZE=10485760 # 10MB in bytesVerify File Types:
Configure allowed file types in collection schema:
{ "image": { "type": "string", "format": "file", "pattern": "\\.(jpg|jpeg|png|gif)$" }}Database Performance Issues
Slow Queries:
// Enable MongoDB slow query loggingdb.setProfilingLevel(1, { slowms: 100 });
// View slow queriesdb.system.profile.find().sort({ ts: -1 }).limit(10);Connection Pool:
# Increase pool size if neededMONGODB_POOL_SIZE=20Missing Indexes:
// Check index usagedb.blog_posts.explain().find({ published: true });
// Add missing indexesdb.blog_posts.createIndex({ field: 1 });Additional Resources
- Expressa GitHub Repository
- Expressa Documentation
- JSON:API Specification
- JSON Schema Documentation
- MongoDB Deployment Guide
- Persistent Volumes Documentation
- Deployment Configuration
- Networking and Traffic Types
Conclusion
Deploying Expressa on Klutch.sh provides you with a powerful, flexible backend that turns your database into a full-featured API with minimal code. Whether you’re building a mobile app, a content management system, or prototyping a new product, Expressa accelerates development by eliminating boilerplate API code while providing enterprise features like authentication, permissions, and file uploads. With Klutch.sh’s managed infrastructure, persistent storage, and MongoDB integration, your Expressa backend is production-ready, scalable, and always available.
Start building your next API in minutes by deploying Expressa on Klutch.sh today and experience the power of schema-driven development.