Skip to main content

Best Practices

Production-ready patterns and recommendations for Invoke functions.

Function Design

Keep Functions Small and Focused

Each function should have a single, well-defined purpose.

// ✅ Good - focused function
export default async function handler(req, res) {
const userId = req.params.userId;
const user = await getUser(userId);
res.json({ user });
}

// ❌ Avoid - doing too much
export default async function handler(req, res) {
// Handles users, orders, payments, notifications...
// 500+ lines of code
}

Stateless Design

Don't rely on global variables or state between invocations.

// ❌ Don't do this
let cache = {};

export default function handler(req, res) {
cache[req.query.key] = req.body.value; // Won't persist
res.json(cache);
}

// ✅ Use KV store
export default async function handler(req, res) {
await kv.set(req.query.key, req.body.value);
res.json({ success: true });
}

Fast Responses

Respond quickly and offload heavy processing.

// ✅ Quick response
export default async function handler(req, res) {
// Queue for processing
await kv.set(`job:${crypto.randomUUID()}`, req.body)

res.status(202).json({
message: 'Job queued',
status: 'processing'
})
}

Error Handling

Always Handle Errors

Use try-catch blocks and return appropriate error responses.

export default async function handler(req, res) {
try {
const result = await performOperation(req.body)
res.json({ success: true, result })
} catch (error) {
console.error('Operation failed:', error)
res.status(500).json({
error: 'Operation failed',
message: error.message
})
}
}

Validate Input

Always validate and sanitize user input.

export default async function handler(req, res) {
// Validate required fields
const { email, name } = req.body

if (!email || !email.includes('@')) {
return res.status(400).json({
error: 'Invalid email address'
})
}

if (!name || name.length < 2) {
return res.status(400).json({
error: 'Name must be at least 2 characters'
})
}

// Process valid input
const user = await createUser({ email, name })
res.json({ user })
}

Graceful Degradation

Handle service failures gracefully.

export default async function handler(req, res) {
try {
const data = await fetch('https://api.example.com/data')
res.json(await data.json())
} catch (error) {
// Fallback to cached data
const cached = await kv.get('cached:data')
if (cached) {
return res.json({ ...cached, fromCache: true })
}

// Last resort
res.status(503).json({
error: 'Service temporarily unavailable'
})
}
}

Security

Protect Sensitive Data

Never log or expose sensitive information.

// ❌ Don't do this
console.log('User password:', req.body.password)
console.log('API key:', process.env.API_KEY)

// ✅ Safe logging
console.log('User login attempt:', {
email: req.body.email,
timestamp: Date.now()
})

Use Environment Variables for Secrets

Store API keys and secrets in environment variables.

// ✅ Safe
const apiKey = process.env.API_KEY
if (!apiKey) {
return res.status(500).json({ error: 'Configuration error' })
}

const response = await fetch('https://api.example.com/data', {
headers: { Authorization: `Bearer ${apiKey}` }
})

Implement Rate Limiting

Protect against abuse.

export default async function handler(req, res) {
const ip = req.ip
const key = `ratelimit:${ip}`

const requests = (await kv.get(key)) || 0

if (requests >= 100) {
return res.status(429).json({
error: 'Too many requests',
retryAfter: 60
})
}

await kv.set(key, requests + 1, 60) // 60 second window

// Process request
res.json({ success: true })
}

Verify Webhooks

Always verify webhook signatures.

import crypto from 'crypto'

function verifySignature(payload, signature, secret) {
const hmac = crypto.createHmac('sha256', secret)
const digest = hmac.update(payload).digest('hex')
return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(digest))
}

export default function handler(req, res) {
const signature = req.get('x-signature')
const secret = process.env.WEBHOOK_SECRET

if (!verifySignature(JSON.stringify(req.body), signature, secret)) {
return res.status(401).json({ error: 'Invalid signature' })
}

// Process webhook
res.json({ success: true })
}

Performance

Cache Frequently Accessed Data

Use KV store for caching.

export default async function handler(req, res) {
const cacheKey = `cache:${req.path}`

// Check cache
const cached = await kv.get(cacheKey)
if (cached) {
return res.json({ ...cached, fromCache: true })
}

// Fetch and cache
const data = await fetchExpensiveData()
await kv.set(cacheKey, data, 300) // 5 minutes

res.json({ ...data, fromCache: false })
}

Minimize External Requests

Batch API calls when possible.

// ❌ Multiple requests
const user = await fetch('/api/user/1')
const posts = await fetch('/api/user/1/posts')
const comments = await fetch('/api/user/1/comments')

// ✅ Single batched request
const data = await fetch('/api/user/1?include=posts,comments')

Stream Large Responses

Don't load everything into memory.

export default async function handler(req, res) {
res.type('application/json')
res.write('[')

let first = true
for await (const item of streamItems()) {
if (!first) res.write(',')
res.write(JSON.stringify(item))
first = false
}

res.write(']')
res.end()
}

Use Appropriate Data Structures

Choose efficient data structures.

// ❌ Inefficient lookup
const users = [
/*...*/
]
const user = users.find(u => u.id === userId)

// ✅ Efficient lookup
const usersMap = new Map(users.map(u => [u.id, u]))
const user = usersMap.get(userId)

Logging and Monitoring

Structured Logging

Log in a structured format for easy parsing.

export default async function handler(req, res) {
console.log(
JSON.stringify({
level: 'info',
message: 'Request received',
method: req.method,
path: req.path,
timestamp: Date.now()
})
)

try {
const result = await processRequest(req)

console.log(
JSON.stringify({
level: 'info',
message: 'Request processed',
duration: 123,
timestamp: Date.now()
})
)

res.json(result)
} catch (error) {
console.log(
JSON.stringify({
level: 'error',
message: 'Request failed',
error: error.message,
stack: error.stack,
timestamp: Date.now()
})
)

res.status(500).json({ error: error.message })
}
}

Track Metrics

Store metrics for monitoring.

export default async function handler(req, res) {
const startTime = Date.now()

try {
const result = await processRequest(req)

// Track success
await trackMetric('requests.success', 1)
await trackMetric('requests.latency', Date.now() - startTime)

res.json(result)
} catch (error) {
// Track failure
await trackMetric('requests.error', 1)
throw error
}
}

async function trackMetric(metric, value) {
const key = `metrics:${metric}:${getTimeBucket()}`
const current = (await kv.get(key)) || 0
await kv.set(key, current + value, 3600)
}

function getTimeBucket() {
// 5-minute buckets
return Math.floor(Date.now() / 300000) * 300000
}

Code Organization

Use Helper Functions

Extract reusable logic.

// helpers.js (if supported)
function validateEmail(email) {
return email && email.includes('@')
}

function sanitizeInput(input) {
return input.trim().substring(0, 1000)
}

// index.js
export default async function handler(req, res) {
const { email, message } = req.body

if (!validateEmail(email)) {
return res.status(400).json({ error: 'Invalid email' })
}

const sanitized = sanitizeInput(message)

await processMessage(email, sanitized)
res.json({ success: true })
}

Document Your Code

Add comments for complex logic.

/**
* Processes webhook from payment provider
*
* @param {Object} req - Express request object
* @param {Object} res - Express response object
*/
export default async function handler(req, res) {
// Verify webhook signature to prevent spoofing
const isValid = verifySignature(req)
if (!isValid) {
return res.status(401).json({ error: 'Invalid signature' })
}

// Extract and validate payment data
const { amount, currency, orderId } = req.body

// Update order status in KV store
await kv.set(`order:${orderId}`, {
status: 'paid',
amount,
currency,
paidAt: Date.now()
})

res.json({ success: true })
}

Testing

Test Locally

Use the Invoke admin panel or CLI to test functions before deployment.

Handle Edge Cases

Test with various inputs.

export default async function handler(req, res) {
const { items } = req.body

// Handle missing data
if (!items) {
return res.status(400).json({ error: 'items required' })
}

// Handle empty array
if (!Array.isArray(items) || items.length === 0) {
return res.json({ total: 0, items: [] })
}

// Handle invalid items
const valid = items.filter(item => item && item.price > 0)

const total = valid.reduce((sum, item) => sum + item.price, 0)

res.json({ total, items: valid })
}

Use TypeScript or C# for Type Safety

Add type safety to catch errors early.

export default async function handler(req, res) {
const { email, name } = req.body
// Validate at runtime since JS has no compile-time types
if (!email || !name) return res.status(400).json({ error: 'Missing fields' })
res.json({ email, name })
}

Next Steps