β±οΈ Estimated Reading Time: 15 minutes
Modern backend development is fragmented. APIs live in Express.js, background jobs run in Bull Queue, workflows use separate orchestration tools, and AI agents exist in their own isolated runtimes. This fragmentation creates complexity, maintenance overhead, and operational challenges.
Motia solves this problem by unifying APIs, background jobs, workflows, and AI agents into a single coherent system. Similar to how React unified frontend development around components, Motia unifies backend development around Steps.
Before starting this tutorial, ensure you have:
First, letβs verify your development environment:
# Check Node.js version
node --version
# Should be 16.x or higher
# Check npm version
npm --version
# Check Python version (optional for multi-language features)
python3 --version
# Should be 3.8 or higher
Motia provides an interactive project generator that sets up everything you need:
# Create a new Motia project
npx motia@latest create -i
# Follow the interactive prompts:
# β
Project name: motia-tutorial
# β
Template: Full-stack (recommended)
# β
Language: TypeScript
# β
Features: API + Background Jobs + AI Integration
This creates a complete project structure:
motia-tutorial/
βββ motia/
β βββ steps/ # Your business logic
β βββ events/ # Event definitions
β βββ workflows/ # Workflow configurations
βββ web/ # Frontend (optional)
βββ package.json
βββ motia.config.js # Framework configuration
Navigate to your project and start the Motia development server:
cd motia-tutorial
# Install dependencies
npm install
# Start the development server
npx motia dev
This launches:
In Motia, everything is a Step. Steps are the fundamental building blocks that can be:
Type | Trigger | Use Case | Example |
---|---|---|---|
api | HTTP Request | REST endpoints | User registration |
event | Message/Topic | Background processing | Email sending |
cron | Schedule | Recurring jobs | Daily reports |
noop | Manual | External processes | Third-party webhooks |
Letβs create a user management API:
// motia/steps/users/create-user.ts
import { api } from '@motia/core';
import { z } from 'zod';
// Input validation schema
const CreateUserSchema = z.object({
name: z.string().min(2),
email: z.string().email(),
role: z.enum(['user', 'admin']).default('user')
});
export default api({
id: 'create-user',
method: 'POST',
path: '/users',
schema: {
body: CreateUserSchema
}
}, async ({ body }) => {
// Simulate user creation
const user = {
id: Math.random().toString(36).substr(2, 9),
...body,
createdAt: new Date().toISOString()
};
// Trigger welcome email event
await motia.trigger('send-welcome-email', {
userId: user.id,
email: user.email,
name: user.name
});
return {
success: true,
user
};
});
Now letβs handle the welcome email as a background job:
// motia/steps/emails/send-welcome-email.ts
import { event } from '@motia/core';
import { z } from 'zod';
const WelcomeEmailSchema = z.object({
userId: z.string(),
email: z.string().email(),
name: z.string()
});
export default event({
id: 'send-welcome-email',
schema: WelcomeEmailSchema
}, async ({ userId, email, name }) => {
// Simulate email sending delay
await new Promise(resolve => setTimeout(resolve, 2000));
console.log(`π§ Welcome email sent to ${name} (${email})`);
// Update user record
await motia.trigger('update-user-status', {
userId,
status: 'welcomed'
});
return {
emailSent: true,
timestamp: new Date().toISOString()
};
});
With the development server running, test your API:
# Create a new user
curl -X POST http://localhost:3001/users \
-H "Content-Type: application/json" \
-d '{
"name": "John Doe",
"email": "john@example.com",
"role": "user"
}'
Expected response:
{
"success": true,
"user": {
"id": "abc123def",
"name": "John Doe",
"email": "john@example.com",
"role": "user",
"createdAt": "2025-09-10T10:30:00.000Z"
}
}
One of Motiaβs powerful features is seamless multi-language integration. Letβs add Python-based AI processing to our workflow.
First, ensure Python dependencies are configured:
# Create Python requirements file
cat > requirements.txt << EOF
openai>=1.0.0
numpy>=1.21.0
pandas>=1.3.0
EOF
# Install Python dependencies
pip3 install -r requirements.txt
Create an AI-powered user analysis step:
# motia/steps/ai/analyze_user_behavior.py
from motia import event
import json
import random
from typing import Dict, Any
@event(
id="analyze-user-behavior",
schema={
"userId": str,
"actions": list,
"metadata": dict
}
)
async def analyze_user_behavior(userId: str, actions: list, metadata: dict) -> Dict[str, Any]:
"""Analyze user behavior patterns using AI"""
# Simulate AI processing
behavior_score = random.uniform(0.1, 1.0)
# Simple behavior analysis
analysis = {
"userId": userId,
"behaviorScore": behavior_score,
"riskLevel": "low" if behavior_score > 0.7 else "medium" if behavior_score > 0.4 else "high",
"recommendations": [],
"processedAt": "2025-09-10T10:30:00.000Z"
}
# Add recommendations based on score
if behavior_score < 0.4:
analysis["recommendations"].append("Increase user engagement activities")
analysis["recommendations"].append("Send personalized content")
elif behavior_score < 0.7:
analysis["recommendations"].append("Monitor user activity closely")
else:
analysis["recommendations"].append("User shows excellent engagement")
print(f"π€ AI Analysis completed for user {userId}: {analysis['riskLevel']} risk")
return analysis
Update your user creation flow to include AI analysis:
// motia/steps/users/create-user.ts (updated)
export default api({
id: 'create-user',
method: 'POST',
path: '/users',
schema: {
body: CreateUserSchema
}
}, async ({ body }) => {
const user = {
id: Math.random().toString(36).substr(2, 9),
...body,
createdAt: new Date().toISOString()
};
// Trigger multiple background processes
await Promise.all([
// Send welcome email (TypeScript)
motia.trigger('send-welcome-email', {
userId: user.id,
email: user.email,
name: user.name
}),
// Analyze user behavior (Python)
motia.trigger('analyze-user-behavior', {
userId: user.id,
actions: ['registration'],
metadata: { source: 'api', userAgent: 'tutorial' }
})
]);
return { success: true, user };
});
Motia makes it easy to create recurring jobs with cron steps.
// motia/steps/reports/daily-summary.ts
import { cron } from '@motia/core';
export default cron({
id: 'daily-summary-report',
schedule: '0 9 * * *', // Every day at 9 AM
}, async () => {
console.log('π Generating daily summary report...');
// Simulate report generation
const report = {
date: new Date().toISOString().split('T')[0],
totalUsers: Math.floor(Math.random() * 1000) + 100,
activeUsers: Math.floor(Math.random() * 500) + 50,
revenue: Math.floor(Math.random() * 10000) + 1000
};
// Send report via email
await motia.trigger('send-report-email', {
reportType: 'daily-summary',
data: report,
recipients: ['admin@example.com']
});
return report;
});
// motia/steps/maintenance/cleanup-logs.ts
import { cron } from '@motia/core';
export default cron({
id: 'cleanup-old-logs',
schedule: '0 2 * * 0', // Every Sunday at 2 AM
}, async () => {
console.log('π§Ή Starting log cleanup process...');
// Simulate cleanup process
const deleted = Math.floor(Math.random() * 100) + 10;
console.log(`β
Cleanup completed: ${deleted} old log entries removed`);
return {
deletedEntries: deleted,
cleanupDate: new Date().toISOString()
};
});
Motia excels at orchestrating complex workflows. Letβs create an e-commerce order processing workflow:
// motia/steps/orders/process-order.ts
import { api } from '@motia/core';
import { z } from 'zod';
const ProcessOrderSchema = z.object({
customerId: z.string(),
items: z.array(z.object({
productId: z.string(),
quantity: z.number().min(1),
price: z.number().min(0)
})),
paymentMethod: z.enum(['credit_card', 'paypal', 'bank_transfer'])
});
export default api({
id: 'process-order',
method: 'POST',
path: '/orders',
schema: {
body: ProcessOrderSchema
}
}, async ({ body }) => {
const orderId = `order_${Date.now()}`;
console.log(`π Processing order ${orderId} for customer ${body.customerId}`);
// Step 1: Validate inventory
await motia.trigger('validate-inventory', {
orderId,
items: body.items
});
// Step 2: Process payment
await motia.trigger('process-payment', {
orderId,
customerId: body.customerId,
amount: body.items.reduce((sum, item) => sum + (item.price * item.quantity), 0),
method: body.paymentMethod
});
// Step 3: Create shipping label (runs after payment)
await motia.trigger('create-shipping-label', {
orderId,
customerId: body.customerId,
items: body.items
});
return {
success: true,
orderId,
status: 'processing',
estimatedDelivery: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString()
};
});
Create the supporting workflow steps:
// motia/steps/orders/validate-inventory.ts
import { event } from '@motia/core';
export default event({
id: 'validate-inventory'
}, async ({ orderId, items }) => {
console.log(`π¦ Validating inventory for order ${orderId}`);
// Simulate inventory check
await new Promise(resolve => setTimeout(resolve, 1000));
const isValid = Math.random() > 0.1; // 90% success rate
if (!isValid) {
throw new Error(`Insufficient inventory for order ${orderId}`);
}
console.log(`β
Inventory validated for order ${orderId}`);
return { validated: true, orderId };
});
// motia/steps/orders/process-payment.ts
import { event } from '@motia/core';
export default event({
id: 'process-payment'
}, async ({ orderId, customerId, amount, method }) => {
console.log(`π³ Processing ${method} payment of $${amount} for order ${orderId}`);
// Simulate payment processing
await new Promise(resolve => setTimeout(resolve, 2000));
const success = Math.random() > 0.05; // 95% success rate
if (!success) {
throw new Error(`Payment failed for order ${orderId}`);
}
console.log(`β
Payment processed successfully for order ${orderId}`);
return { paid: true, orderId, transactionId: `txn_${Date.now()}` };
});
The Motia Workbench provides powerful debugging capabilities:
Access the workbench at: http://localhost:3000
Enhance your steps with structured logging:
// motia/steps/users/create-user.ts (with logging)
import { api, logger } from '@motia/core';
export default api({
id: 'create-user',
method: 'POST',
path: '/users'
}, async ({ body }) => {
const startTime = Date.now();
logger.info('User creation started', {
email: body.email,
role: body.role,
timestamp: new Date().toISOString()
});
try {
const user = {
id: Math.random().toString(36).substr(2, 9),
...body,
createdAt: new Date().toISOString()
};
await motia.trigger('send-welcome-email', {
userId: user.id,
email: user.email,
name: user.name
});
logger.info('User created successfully', {
userId: user.id,
executionTime: Date.now() - startTime
});
return { success: true, user };
} catch (error) {
logger.error('User creation failed', {
error: error.message,
email: body.email,
executionTime: Date.now() - startTime
});
throw error;
}
});
Create environment-specific configurations:
// motia.config.js
module.exports = {
development: {
port: 3001,
workbench: {
enabled: true,
port: 3000
},
database: {
url: 'sqlite://./motia-dev.db'
}
},
production: {
port: process.env.PORT || 8080,
workbench: {
enabled: false // Disable in production
},
database: {
url: process.env.DATABASE_URL
},
redis: {
url: process.env.REDIS_URL
}
}
};
Create a production-ready Dockerfile:
# Dockerfile
FROM node:18-alpine
WORKDIR /app
# Copy package files
COPY package*.json ./
COPY requirements.txt ./
# Install Node.js dependencies
RUN npm ci --only=production
# Install Python and dependencies
RUN apk add --no-cache python3 py3-pip
RUN pip3 install -r requirements.txt
# Copy application code
COPY . .
# Build the application
RUN npm run build
EXPOSE 8080
CMD ["npm", "start"]
# docker-compose.yml
version: '3.8'
services:
motia:
build: .
ports:
- "3000:3000" # Workbench
- "3001:3001" # API
environment:
- NODE_ENV=development
- DATABASE_URL=sqlite://./motia.db
volumes:
- .:/app
- /app/node_modules
depends_on:
- redis
redis:
image: redis:7-alpine
ports:
- "6379:6379"
Organize your steps by domain:
motia/steps/
βββ users/
β βββ create-user.ts
β βββ update-profile.ts
β βββ delete-account.ts
βββ orders/
β βββ process-order.ts
β βββ validate-inventory.ts
β βββ create-shipping-label.ts
βββ notifications/
β βββ send-email.ts
β βββ send-sms.ts
β βββ push-notification.ts
βββ analytics/
βββ track-event.ts
βββ generate-report.ts
Implement robust error handling:
// motia/steps/utils/error-handler.ts
import { event } from '@motia/core';
export default event({
id: 'handle-step-error'
}, async ({ stepId, error, context, retryCount = 0 }) => {
console.error(`β Step ${stepId} failed:`, error);
// Implement retry logic
if (retryCount < 3 && isRetryableError(error)) {
console.log(`π Retrying step ${stepId} (attempt ${retryCount + 1})`);
await new Promise(resolve => setTimeout(resolve, 1000 * Math.pow(2, retryCount)));
return motia.trigger(stepId, {
...context,
__retry: retryCount + 1
});
}
// Send alert for persistent failures
await motia.trigger('send-alert', {
level: 'error',
message: `Step ${stepId} failed after ${retryCount} retries`,
error: error.message,
context
});
throw error;
});
function isRetryableError(error: Error): boolean {
const retryablePatterns = [
'ECONNRESET',
'TIMEOUT',
'NETWORK_ERROR',
'RATE_LIMIT'
];
return retryablePatterns.some(pattern =>
error.message.includes(pattern)
);
}
Optimize step performance:
// motia/steps/optimized/batch-processor.ts
import { event } from '@motia/core';
export default event({
id: 'batch-process-users',
concurrency: 5 // Limit concurrent executions
}, async ({ userIds }) => {
// Process users in batches
const BATCH_SIZE = 10;
const results = [];
for (let i = 0; i < userIds.length; i += BATCH_SIZE) {
const batch = userIds.slice(i, i + BATCH_SIZE);
// Process batch in parallel
const batchResults = await Promise.all(
batch.map(userId => processUser(userId))
);
results.push(...batchResults);
}
return { processed: results.length, results };
});
Letβs test our complete application:
# Start the development server
npx motia dev
# In another terminal, test the user creation flow
curl -X POST http://localhost:3001/users \
-H "Content-Type: application/json" \
-d '{
"name": "Alice Johnson",
"email": "alice@example.com",
"role": "admin"
}'
# Test the order processing workflow
curl -X POST http://localhost:3001/orders \
-H "Content-Type: application/json" \
-d '{
"customerId": "cust_12345",
"items": [
{"productId": "prod_1", "quantity": 2, "price": 29.99},
{"productId": "prod_2", "quantity": 1, "price": 49.99}
],
"paymentMethod": "credit_card"
}'
http://localhost:3000
in your browserCongratulations! Youβve successfully learned how to:
β Set up Motia from scratch β Create API endpoints with validation β Build background job processing with events β Implement scheduled tasks with cron steps β Integrate multiple languages (TypeScript + Python) β Design complex workflows with step dependencies β Monitor and debug applications in real-time β Deploy to production with Docker
Motia is transforming how we build backend systems. What will you build next?