Table of Contents
Introduction
Modern backend development often requires juggling multiple frameworks: Express for APIs, BullMQ for queues, Temporal for workflows, and various tools for observability. Motia changes this paradigm by unifying all these concerns around a single core primitive called the Step.
In this comprehensive guide, we'll explore how Motia revolutionizes backend development, making it as simple as React made frontend development.
A complete working example with all features discussed in this article is available on GitHub:
https://github.com/khuongdo/motia-backend-sample
Clone and run:
git clone https://github.com/khuongdo/motia-backend-sample.git
cd motia-backend-sample
npm install
cp .env.example .env
docker-compose up -d
npm run dev
The repository includes:
- Full TypeScript + Python implementation
- i18n with 3 languages (English, Vietnamese, Japanese)
- Authentication & authorization
- Error handling & validation
- Database integration
- Rate limiting
- Scheduled jobs
- AI integration examples
- Docker deployment setup
What is Motia?
Motia is a multi-language backend framework that unifies:
- REST APIs
- Background jobs and queues
- Durable workflows
- Real-time streams
- AI agents and agentic workflows
- Built-in observability and state management
All of this is accomplished through one fundamental building block: the Step.
The Core Primitive: Steps
Just as React introduced components to simplify frontend development, Motia introduces Steps to unify backend concerns. A Step is simply a file containing:
- Config: Defines execution timing, naming, and triggers
- Handler: Contains the business logic
// message.step.ts
export const config = {
name: 'SendMessage',
type: 'api',
path: '/messages',
method: 'POST',
emits: ['message.sent']
};
export const handler = async (req, { emit, logger }) => {
logger.info('Sending message', { text: req.body.text });
await emit({
topic: 'message.sent',
data: { text: req.body.text, timestamp: Date.now() }
});
return { status: 200, body: { ok: true } };
};
Key Features
1. Multi-Language Support
Motia supports polyglot development, allowing you to write different Steps in different languages within the same project:
- TypeScript/JavaScript (Stable)
- Python (Stable)
- Ruby (Beta)
- Go (Coming Soon)
This is particularly valuable for AI workflows where cutting-edge tools are often Python-only:
# sentiment_analysis_step.py
config = {
"name": "AnalyzeSentiment",
"type": "event",
"subscribes": ["message.sent"]
}
def handler(input, context):
# Use Python AI libraries
from transformers import pipeline
sentiment = pipeline("sentiment-analysis")
result = sentiment(input["data"]["text"])
context.logger.info(f"Sentiment: {result}")
return result
2. Event-Driven Architecture
Steps communicate through topics, decoupling producers from consumers:
// processor.step.ts
export const config = {
name: 'ProcessMessage',
type: 'event',
subscribes: ['message.sent']
};
export const handler = async (input, { state, logger }) => {
logger.info('Processing message', input);
// Store in persistent state
await state.set(`message:${input.data.timestamp}`, input.data);
// Process and emit further events
// Built-in retry mechanisms handle failures
};
3. Built-in State Management
Persistent key-value storage spans across all Steps, eliminating the need for external databases for simple state needs:
export const handler = async (req, { state }) => {
// Atomic operations
const count = await state.get('message_count') || 0;
await state.set('message_count', count + 1);
// State is shared across all Steps
const history = await state.get('message_history') || [];
await state.set('message_history', [...history, req.body]);
};
4. Scheduled Jobs (Cron)
Define recurring tasks with simple configuration:
// cleanup.step.ts
export const config = {
name: 'CleanupOldMessages',
type: 'cron',
schedule: '0 0 * * *' // Daily at midnight
};
export const handler = async (_, { state, logger }) => {
logger.info('Running cleanup job');
const cutoff = Date.now() - (7 * 24 * 60 * 60 * 1000); // 7 days
// Cleanup logic here
};
5. Real-Time Streaming
Stream data to connected clients without complex infrastructure:
export const handler = async (req, { streams }) => {
// Define stream structure
const stream = streams.create({
id: req.params.chatId,
schema: { messages: [] }
});
// Any changes stream to all subscribed clients automatically
stream.update({ messages: [...stream.data.messages, newMessage] });
};
6. Built-in Observability
The Motia Workbench provides:
- Real-time logs with automatic trace IDs
- Request flow visualization
- State inspection
- Dependency diagrams between Steps
- Performance metrics
Essential Backend Features
Error Handling
Motia provides robust error handling with built-in retry mechanisms for event-based Steps:
// error-handling.step.ts
export const config = {
name: 'ProcessPayment',
type: 'event',
subscribes: ['payment.requested'],
retry: {
maxAttempts: 3,
backoff: 'exponential',
timeout: 30000
}
};
export const handler = async (input, { logger, emit }) => {
try {
// Process payment
const result = await processPayment(input.data);
await emit({
topic: 'payment.succeeded',
data: result
});
return { success: true };
} catch (error) {
logger.error('Payment processing failed', {
error: error.message,
input
});
// Motia automatically retries based on config
// If max attempts reached, emit failure event
if (error.retryCount >= 3) {
await emit({
topic: 'payment.failed',
data: { error: error.message, input }
});
}
throw error; // Trigger retry
}
};
Input Validation
Use Zod or other validation libraries for type-safe APIs:
// validated-api.step.ts
import { z } from 'zod';
const CreateUserSchema = z.object({
email: z.string().email(),
name: z.string().min(2).max(100),
age: z.number().int().min(18)
});
export const config = {
name: 'CreateUser',
type: 'api',
path: '/users',
method: 'POST'
};
export const handler = async (req, { logger }) => {
try {
// Validate input
const data = CreateUserSchema.parse(req.body);
// Process valid data
logger.info('Creating user', data);
return {
status: 201,
body: { id: generateId(), ...data }
};
} catch (error) {
if (error instanceof z.ZodError) {
return {
status: 400,
body: {
error: 'Validation failed',
details: error.errors
}
};
}
throw error;
}
};
Internationalization (i18n)
Implement i18n using popular libraries like i18next:
// i18n-config.ts
import i18next from 'i18next';
i18next.init({
lng: 'en',
resources: {
en: {
translation: {
'welcome': 'Welcome',
'error.notFound': 'Resource not found'
}
},
vi: {
translation: {
'welcome': 'Chào mừng',
'error.notFound': 'Không tìm thấy tài nguyên'
}
},
ja: {
translation: {
'welcome': 'ようこそ',
'error.notFound': 'リソースが見つかりません'
}
}
}
});
export default i18next;
// localized-api.step.ts
import i18n from './i18n-config';
export const config = {
name: 'GetLocalizedContent',
type: 'api',
path: '/content',
method: 'GET'
};
export const handler = async (req) => {
const lang = req.headers['accept-language']?.split(',')[0] || 'en';
return {
status: 200,
body: {
message: i18n.t('welcome', { lng: lang }),
lang
}
};
};
Middleware & Authentication
Configure custom middleware in motia.config.ts:
// motia.config.ts
import { defineConfig } from 'motia';
import jwt from 'jsonwebtoken';
export default defineConfig({
middleware: {
pre: [
// CORS middleware
(req, res, next) => {
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
next();
},
// Authentication middleware
(req, res, next) => {
const publicPaths = ['/auth/login', '/auth/register'];
if (publicPaths.includes(req.path)) {
return next();
}
const token = req.headers.authorization?.replace('Bearer ', '');
if (!token) {
return res.status(401).json({ error: 'Unauthorized' });
}
try {
req.user = jwt.verify(token, process.env.JWT_SECRET);
next();
} catch (error) {
res.status(401).json({ error: 'Invalid token' });
}
}
]
}
});
Environment Configuration
Manage environment-specific settings:
// config.step.ts
export const config = {
name: 'GetConfig',
type: 'api',
path: '/config',
method: 'GET'
};
export const handler = async () => {
return {
status: 200,
body: {
environment: process.env.NODE_ENV,
apiUrl: process.env.API_URL,
version: process.env.APP_VERSION
}
};
};
Database Integration
Integrate with any database (PostgreSQL, MongoDB, etc.):
// db-integration.step.ts
import { Pool } from 'pg';
const pool = new Pool({
connectionString: process.env.DATABASE_URL
});
export const config = {
name: 'GetUsers',
type: 'api',
path: '/users',
method: 'GET'
};
export const handler = async (req, { logger }) => {
try {
const result = await pool.query('SELECT * FROM users LIMIT 10');
return {
status: 200,
body: result.rows
};
} catch (error) {
logger.error('Database query failed', error);
return {
status: 500,
body: { error: 'Internal server error' }
};
}
};
Rate Limiting
Implement rate limiting for API protection:
// rate-limiter.step.ts
export const config = {
name: 'RateLimitedEndpoint',
type: 'api',
path: '/api/limited',
method: 'GET'
};
const rateLimits = new Map();
export const handler = async (req, { state }) => {
const ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress;
const key = `ratelimit:${ip}`;
const requests = await state.get(key) || [];
const now = Date.now();
const windowMs = 60000; // 1 minute
// Filter requests within window
const recentRequests = requests.filter(t => now - t < windowMs);
if (recentRequests.length >= 100) {
return {
status: 429,
body: { error: 'Too many requests' }
};
}
await state.set(key, [...recentRequests, now]);
return { status: 200, body: { ok: true } };
};
Getting Started
Installation
npx motia@latest create my-backend
cd my-backend
npm run dev
This launches the Motia Workbench on http://localhost:3000 with zero configuration.
Project Structure
my-backend/
├── src/
│ ├── api/
│ │ ├── users.step.ts
│ │ └── auth.step.ts
│ ├── events/
│ │ ├── email-notification.step.ts
│ │ └── analytics.step.ts
│ ├── cron/
│ │ └── cleanup.step.ts
│ ├── ai/
│ │ └── sentiment_analysis_step.py
│ └── config/
│ ├── i18n.ts
│ └── database.ts
├── motia.config.ts
├── package.json
└── .env
File Naming Convention
Motia auto-discovers files following these patterns:
.step.ts/.step.js(TypeScript/JavaScript)_step.py(Python).step.rb(Ruby)
Deployment Options
1. Motia Cloud (Recommended)
npm run deploy
Features:
- Atomic blue/green deployments
- One-click rollbacks
- Automatic scaling
- Built-in monitoring
2. Self-Hosted
docker run -p 3000:3000 \
-e DATABASE_URL=$DATABASE_URL \
-v ./src:/app/src \
motia/motia-runtime
3. Platform Partners
Deploy to Railway, Fly.io, or any Node.js hosting with:
npm run build
npm start
Real-World Use Cases
AI Agent Workflows
// ai-agent.step.ts
export const config = {
name: 'AIContentModerator',
type: 'event',
subscribes: ['content.submitted']
};
export const handler = async (input, { emit, logger }) => {
// Call AI model (OpenAI, Anthropic, etc.)
const moderation = await checkContent(input.data.text);
if (moderation.flagged) {
await emit({
topic: 'content.flagged',
data: { ...input.data, reason: moderation.reason }
});
} else {
await emit({
topic: 'content.approved',
data: input.data
});
}
logger.info('Content moderated', { flagged: moderation.flagged });
};
Multi-Step Workflows
// Order processing workflow
// 1. Create order (API)
// 2. Process payment (Event)
// 3. Update inventory (Event)
// 4. Send confirmation email (Event)
// 5. Schedule delivery (Event)
// Each step emits events for the next, creating durable workflows
Comparison with Other Frameworks
| Feature | Motia | Express + BullMQ + Temporal |
|---|---|---|
| Setup Complexity | ⭐ Simple | ⭐⭐⭐⭐ Complex |
| Multi-Language | ✅ Built-in | ❌ Separate services |
| State Management | ✅ Built-in | ❌ External DB required |
| Observability | ✅ Built-in Workbench | ❌ Setup required |
| Event Streaming | ✅ Native | ❌ Additional infrastructure |
| Learning Curve | ⭐ Easy | ⭐⭐⭐⭐ Steep |
Best Practices
1. Organize by Feature
src/
├── users/
│ ├── create-user.step.ts
│ ├── get-user.step.ts
│ └── update-user.step.ts
├── orders/
│ ├── create-order.step.ts
│ └── process-payment.step.ts
2. Use Environment Variables
Never hardcode sensitive data. Use .env files:
DATABASE_URL=postgresql://...
JWT_SECRET=your-secret-key
API_KEY=your-api-key
3. Leverage Type Safety
Use TypeScript and validation libraries for robust APIs.
4. Monitor with Workbench
Use the built-in Workbench during development to trace requests and debug issues.
5. Test Incrementally
Test each Step independently before integrating into workflows.
Conclusion
Motia represents a paradigm shift in backend development, offering the same simplicity that React brought to frontend development. By unifying APIs, background jobs, workflows, and AI agents around a single primitive, Motia eliminates the complexity of managing multiple frameworks while providing production-ready features like observability, state management, and multi-language support.
Whether you're building a simple REST API or a complex AI-powered workflow system, Motia provides the tools and developer experience to ship faster without sacrificing scalability or maintainability.
Get Started Today
npx motia@latest create
Resources
Have you tried Motia? Share your experience in the comments below!