Skip to main content

Overview

Server functions let you run backend logic inside your Canvas app. Write TypeScript or JavaScript handlers that execute server-side with access to environment variables, a built-in KV store, and fetch — without managing infrastructure.

How it works

  1. Include server function source files in your deploy’s source_files map
  2. Files under api/ become HTTP endpoints at /functions/
  3. Canvas transpiles TypeScript → JavaScript via esbuild and executes your handler in a sandboxed async context
Source fileEndpoint
api/hello.tsGET/POST /functions/hello
api/search.jsGET/POST /functions/search
api/data/export.tsGET/POST /functions/data/export

Writing a server function

Export a default function that receives a context object and returns a response:
// api/hello.ts
export default async function handler(ctx) {
  const name = ctx.req.query.name || 'world'
  return {
    status: 200,
    body: { message: `Hello, ${name}!` },
  }
}

Context object

Your handler receives a ctx object with:
PropertyTypeDescription
ctx.req.methodstringHTTP method (GET, POST, etc.)
ctx.req.pathstringRequest path
ctx.req.queryobjectURL query parameters
ctx.req.bodyanyParsed JSON body (for POST/PUT)
ctx.req.headersobjectRequest headers (excluding internal headers)
ctx.envobjectApp environment variables
ctx.kvobjectKey-value store (see below)

Return value

Return an object with:
FieldTypeDefaultDescription
statusnumber200HTTP status code
bodyanyResponse body (object → JSON, string → text)
headersobject{}Custom response headers

Key-value store

Every app has a built-in Redis-backed KV store, accessible in server functions via ctx.kv:
// api/counter.ts
export default async function handler(ctx) {
  const current = await ctx.kv.get('visit_count')
  const count = parseInt(current || '0') + 1
  await ctx.kv.set('visit_count', String(count))

  return {
    body: { visits: count },
  }
}
MethodSignatureDescription
getkv.get(key: string): Promise<string | null>Read a value
setkv.set(key: string, value: string, ttl?: number): Promise<void>Write a value (optional TTL in seconds, default 30 days)
delkv.del(key: string): Promise<void>Delete a key
keyskv.keys(): Promise<string[]>List all keys for this app

Calling the Mixpeek API

Use fetch to call the Mixpeek API through the Canvas proxy — credentials are injected automatically:
// api/search.ts
export default async function handler(ctx) {
  const { query } = ctx.req.body

  const res = await fetch('https://api.mixpeek.com/v1/retrievers/ret_abc123/execute', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${ctx.env.MIXPEEK_API_KEY}`,
      'X-Namespace': ctx.env.MIXPEEK_NAMESPACE_ID,
    },
    body: JSON.stringify({ inputs: { query }, settings: { limit: 10 } }),
  })

  return { body: await res.json() }
}

Deploying with source files

Include your server functions in the source_files field when deploying:
curl -X POST https://api.mixpeek.com/v1/apps/$APP_ID/deploy \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "source": "cli_upload",
    "bundle_s3_key": "$BUNDLE_KEY",
    "environment": "production",
    "message": "Add search API route",
    "source_files": {
      "api/hello.ts": "export default async function handler(ctx) {\n  return { body: { message: \"Hello!\" } }\n}",
      "api/search.ts": "export default async function handler(ctx) {\n  // search logic here\n}"
    }
  }'

Runtime logging

console.log, console.warn, console.error, and console.info calls in server functions are captured and sent to the runtime logs system. View them in Studio or query via API.
// api/process.ts
export default async function handler(ctx) {
  console.log('Processing request', ctx.req.method, ctx.req.path)

  try {
    const result = await doWork(ctx.req.body)
    console.info('Success:', result.id)
    return { body: result }
  } catch (err) {
    console.error('Failed:', err.message)
    return { status: 500, body: { error: err.message } }
  }
}

Limitations

  • Execution timeout: 30 seconds per request
  • Request body size: 1 MB max
  • No filesystem access: Server functions run in a sandboxed context
  • No npm imports: Only fetch and the ctx APIs are available in the execution context
  • TypeScript only: .ts and .js files are supported (transpiled via esbuild)