Serverless Functions — Browser IDE, Deploy & Test
Create serverless functions directly in the browser using a full-featured Monaco Editor. Test with custom payloads, publish when ready, and connect to API routes or cron jobs.
// Stripe Webhook Handler
Paste this into the function editor. Attach the shared-auth layer for signature verification. Connect to a POST route in the API Gateway.
// Requires layer: shared-auth (provides crypto helpers)
const crypto = require('crypto');
export const handler = async (event) => {
const STRIPE_SECRET = process.env.STRIPE_WEBHOOK_SECRET || 'whsec_test_...';
const sig = event.headers['stripe-signature'];
const body = typeof event.body === 'string' ? event.body : JSON.stringify(event.body);
// Verify Stripe signature
const timestamp = sig?.split(',').find(s => s.startsWith('t='))?.split('=')[1];
const v1 = sig?.split(',').find(s => s.startsWith('v1='))?.split('=')[1];
const expected = crypto
.createHmac('sha256', STRIPE_SECRET)
.update(timestamp + '.' + body)
.digest('hex');
if (v1 !== expected) {
return { statusCode: 401, body: { error: 'Invalid signature' } };
}
const { type, data } = JSON.parse(body);
switch (type) {
case 'checkout.session.completed':
return {
statusCode: 200,
body: {
action: 'fulfill_order',
customer: data.object.customer_email,
amount: data.object.amount_total / 100,
currency: data.object.currency
}
};
case 'invoice.payment_failed':
return {
statusCode: 200,
body: {
action: 'notify_customer',
customer: data.object.customer_email,
invoice_id: data.object.id
}
};
case 'customer.subscription.deleted':
return {
statusCode: 200,
body: {
action: 'cancel_access',
subscription_id: data.object.id,
customer: data.object.customer
}
};
default:
return { statusCode: 200, body: { received: true, type } };
}
};Test payload you can paste into the Test Panel:
{
"headers": {
"stripe-signature": "t=1234567890,v1=test_sig"
},
"body": {
"type": "checkout.session.completed",
"data": {
"object": {
"customer_email": "alice@example.com",
"amount_total": 4999,
"currency": "usd"
}
}
}
}// CSV Parser with Validation
Upload a CSV parsing layer, then use this function to accept raw CSV and return clean JSON. Handles quoted fields, trims whitespace, validates required columns.
// No layer needed — pure JS implementation
export const handler = async (event) => {
const { csv, requiredColumns = [], delimiter = ',' } = event.body;
if (!csv || typeof csv !== 'string') {
return { statusCode: 400, body: { error: 'Missing "csv" field in body' } };
}
const lines = csv.trim().split('\n');
if (lines.length < 2) {
return { statusCode: 400, body: { error: 'CSV must have a header row and at least one data row' } };
}
// Parse header
const headers = lines[0].split(delimiter).map(h => h.trim().replace(/^"|"$/g, ''));
// Validate required columns
const missing = requiredColumns.filter(col => !headers.includes(col));
if (missing.length > 0) {
return { statusCode: 400, body: { error: 'Missing columns: ' + missing.join(', ') } };
}
// Parse rows
const data = [];
const errors = [];
for (let i = 1; i < lines.length; i++) {
if (!lines[i].trim()) continue;
const values = lines[i].split(delimiter).map(v => v.trim().replace(/^"|"$/g, ''));
if (values.length !== headers.length) {
errors.push({ row: i + 1, message: 'Column count mismatch: expected ' + headers.length + ', got ' + values.length });
continue;
}
const row = {};
headers.forEach((h, idx) => { row[h] = values[idx]; });
data.push(row);
}
return {
statusCode: 200,
body: {
total: data.length,
columns: headers,
errors: errors.length > 0 ? errors : undefined,
data
}
};
};Test payload:
{
"body": {
"csv": "name,email,role\nAlice,alice@example.com,admin\nBob,bob@corp.io,developer\nCarol,carol@test.com,viewer",
"requiredColumns": ["name", "email"],
"delimiter": ","
}
}// API Proxy & Aggregator
Combine multiple external API calls into one response. Uses built-in fetch.
export const handler = async (event) => {
const userId = event.body?.userId || event.queryStringParameters?.userId;
if (!userId) {
return { statusCode: 400, body: { error: 'userId is required' } };
}
// Fetch from multiple APIs in parallel
const [userRes, postsRes, todosRes] = await Promise.allSettled([
fetch('https://jsonplaceholder.typicode.com/users/' + userId),
fetch('https://jsonplaceholder.typicode.com/posts?userId=' + userId),
fetch('https://jsonplaceholder.typicode.com/todos?userId=' + userId + '&_limit=5'),
]);
const parse = async (res) => {
if (res.status === 'fulfilled' && res.value.ok) return res.value.json();
return null;
};
const user = await parse(userRes);
const posts = await parse(postsRes);
const todos = await parse(todosRes);
if (!user) {
return { statusCode: 404, body: { error: 'User not found' } };
}
return {
statusCode: 200,
body: {
user: { id: user.id, name: user.name, email: user.email, company: user.company?.name },
stats: { totalPosts: posts?.length || 0, totalTodos: todos?.length || 0 },
recentPosts: (posts || []).slice(0, 3).map(p => ({ id: p.id, title: p.title })),
pendingTodos: (todos || []).filter(t => !t.completed).map(t => ({ id: t.id, title: t.title })),
}
};
};Test payload:
{
"body": { "userId": 1 }
}// Using Layers in Functions
Attach a layer to your function, then require() it by the layer's module name. Layers are loaded into the runtime path before your function executes.
// Requires layers: db-pool, http-utils
// Attach these in the function editor's Layers panel
const db = require('db-pool'); // -> from db-pool layer ZIP
const http = require('http-utils'); // -> from http-utils layer ZIP
export const handler = async (event) => {
try {
const { id } = event.body;
if (!id) return http.error(400, 'Missing id');
const result = await db.query('SELECT * FROM users WHERE id = $1', [id]);
if (result.rows.length === 0) return http.notFound('User not found');
return http.json(result.rows[0]);
} catch (err) {
return http.error(500, err.message);
}
};// Requires layer: email-sender
const mailer = require('email-sender');
export const handler = async (event) => {
const { to, subject, name } = event.body;
if (!to || !subject) {
return { statusCode: 400, body: { error: 'Missing "to" or "subject"' } };
}
const html = '<h1>Hello ' + (name || 'there') + '</h1>'
+ '<p>This email was sent from a moqapi serverless function.</p>'
+ '<p>Time: ' + new Date().toISOString() + '</p>';
const result = await mailer.send({
to,
subject,
html,
from: process.env.SMTP_FROM || 'noreply@moqapi.dev'
});
return {
statusCode: 200,
body: { sent: true, messageId: result.messageId }
};
};// Draft → Publish Workflow
Write Draft Code
Edit in the Monaco IDE. Changes are saved as draft automatically.
Test with Payloads
Invoke your function in the Test Panel with the JSON payloads shown above.
Publish → Connect to API Route
Click Publish, then bind it to an API Gateway route (e.g. POST /webhooks/stripe).
// Invoke via curl
# Invoke a function directly
curl -X POST https://moqapi.dev/api/functions/<function-id>/invoke \
-H "Content-Type: application/json" \
-d '{"body": {"csv": "name,email\nAlice,alice@test.com"}}'
# Invoke via project URL (project ID + function name path)
curl -X POST https://moqapi.dev/api/invoke/{projectId}/fn/webhooks/stripe \
-H "Content-Type: application/json" \
-H "stripe-signature: t=123,v1=abc" \
-d '{"type":"checkout.session.completed","data":{"object":{"customer_email":"a@b.com","amount_total":999,"currency":"usd"}}}'