Runtime Reference
This reference covers the full contract for Python and Node.js runtimes — event object shape, response format, init() configuration, CORS, body handling, and error handling.
Python Runtimes
E2E FaaS provides three Python runtime families. Choose based on your application requirements.
| Runtime | Best For |
|---|---|
| FastAPI | REST APIs, microservices, AI APIs, request validation, OpenAPI documentation |
| Flask | Migrating existing Flask apps, simple APIs, Flask extensions |
| HTTP | Lightweight webhooks, event processing, scheduled tasks, serverless utilities |
Choosing a Python Runtime
FastAPI
Recommended for most new Python applications.
- Available version: Python 3.11
- Use when: building production APIs, needing request validation or type hints, requiring auto-generated OpenAPI docs, building AI APIs
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
async def root():
return {"message": "Hello World"}
FastAPI manages routing, middleware, request validation, CORS, and OpenAPI generation. No platform-specific init() configuration is required.
Flask
Simple and flexible web framework.
- Available version: Python 3.11
- Use when: migrating existing Flask applications, building lightweight services, using Flask extensions
from flask import Flask
app = Flask(__name__)
@app.route("/")
def home():
return {"message": "Hello World"}
Flask manages routing, middleware, and request handling. No platform-specific init() configuration is required.
HTTP Runtime
Lightweight function-based model. No web framework required.
- Available versions: Python 3.9, 3.10, 3.11, 3.12, 3.13, 3.14
- Use when: building webhooks, processing events, running scheduled tasks, minimizing cold-start overhead
import json
async def handler(event, context):
return {
"statusCode": 200,
"body": json.dumps({"message": "Hello World"})
}
Python HTTP Runtime — Event Object
Every request is converted into an event dictionary.
| Field | Description |
|---|---|
event["httpMethod"] | HTTP method: "GET", "POST", etc. |
event["path"] | URL path, e.g. "/users/42" |
event["headers"] | All request headers as key-value pairs |
event["queryStringParameters"] | Query params as a dict, or None |
event["body"] | Request body as a string (UTF-8) or base64 string for binary. None if empty. |
event["rawBody"] | Raw bytes of the request body |
event["isBase64Encoded"] | True when body is base64 (binary uploads) |
import json
async def handler(event, context):
body = json.loads(event["body"])
page = (event.get("queryStringParameters") or {}).get("page")
method = event["httpMethod"]
return {"statusCode": 200}
Python HTTP Runtime — Response Object
Every handler must return a dictionary. Only statusCode is required.
# JSON response
import json
return {
"statusCode": 200,
"headers": {"Content-Type": "application/json"},
"body": json.dumps({"success": True})
}
# Redirect
return {
"statusCode": 302,
"headers": {"Location": "https://example.com"}
}
# Cookies
return {
"statusCode": 200,
"multiValueHeaders": {"Set-Cookie": ["session=abc; HttpOnly"]},
"body": json.dumps({"ok": True})
}
# No content
return {"statusCode": 204, "body": None}
Python HTTP Runtime — init()
init() is optional. It configures CORS and body handling at startup. If not defined, sensible defaults apply.
init() must be synchronous. Do not use async def or perform I/O inside init().
def init():
return {
"cors": {
"origins": ["*"],
"credentials": False
},
"body": {
"json": True,
"urlencoded": False,
"content_types": [],
"max_bytes": 10 * 1024 * 1024 # 10MB default, 100MB max
}
}
Defaults when init() is not defined
| Setting | Default | Meaning |
|---|---|---|
cors.origins | ["*"] | Any domain can call your function |
cors.credentials | False | Cookies and auth headers not forwarded |
body.json | True | JSON requests accepted |
body.urlencoded | False | Form submissions rejected with 415 |
body.content_types | [] | No additional content types |
body.max_bytes | 10 MB | Requests with larger bodies get 413 |
Platform validation
The function will fail to start if:
init()is asynchronous (async def)init()does not return a dictionarycors.originsis invalidcredentialsisTruewith wildcard originsmax_bytesexceeds 100 MB
CORS Configuration (Python)
# Allow any origin (default)
def init():
return {"cors": {"origins": ["*"]}}
# Restrict to specific domains
def init():
return {"cors": {"origins": ["https://app.example.com"]}}
# Enable cookies and Authorization headers
def init():
return {
"cors": {
"origins": ["https://app.example.com"],
"credentials": True # wildcard origins cannot be used here
}
}
Body Configuration (Python)
# Form submissions
def init():
return {"body": {"urlencoded": True}}
# File uploads (50MB limit)
import re
def init():
return {
"body": {
"content_types": [re.compile(r"^multipart/form-data")],
"max_bytes": 50 * 1024 * 1024
}
}
# XML API
def init():
return {
"body": {
"json": False,
"content_types": ["text/xml", "application/xml"]
}
}
# Images
import re
def init():
return {"body": {"content_types": [re.compile(r"^image/")]}}
# PDF
def init():
return {"body": {"content_types": ["application/pdf"]}}
For binary content (multipart, images, PDF), use event["rawBody"] (bytes) instead of event["body"].
Async vs Sync Handlers (Python)
Both handler types are supported.
# Async — recommended for database operations, HTTP requests, external APIs
async def handler(event, context):
pass
# Sync — suitable for lightweight processing, CPU-bound logic
def handler(event, context):
pass
Error Handling (Python)
import json
async def handler(event, context):
try:
body = json.loads(event["body"])
if not body.get("email"):
return {
"statusCode": 400,
"headers": {"Content-Type": "application/json"},
"body": json.dumps({"error": "email required"})
}
return {"statusCode": 200, "body": json.dumps({"ok": True})}
except Exception as err:
print(err)
return {
"statusCode": 500,
"headers": {"Content-Type": "application/json"},
"body": json.dumps({"error": "Internal Server Error"})
}
init() Quick Reference (Python)
| Goal | init() config |
|---|---|
| Use defaults | Don't define init() — JSON + 10MB + open CORS |
| Lock CORS | cors: { "origins": ["https://app.com"] } |
| Enable cookies | cors: { "origins": ["https://app.com"], "credentials": True } |
| Accept forms | body: { "urlencoded": True } |
| File uploads | body: { "content_types": [re.compile(r"^multipart/form-data")], "max_bytes": 50 * 1024 * 1024 } |
| XML API | body: { "json": False, "content_types": ["text/xml"] } |
| Images | body: { "content_types": [re.compile(r"^image/")] } |
| Strict size limit | body: { "max_bytes": 1024 * 1024 } — 413 on >1MB |
Node.js Runtimes
Applies to Node.js HTTP runtimes (Node 20, 22, 24). All three runtime versions share the same event, response, and configuration interface — code written for one works on any without changes.
This section applies to Node.js HTTP runtimes (Node 20/22/24) which use ESM format (index.mjs, export const handler). For Express runtimes (Node 18/20), refer to the Express handler syntax.
The Event Object
Every incoming HTTP request is converted into an event object passed to your handler.
| Field | Type | Description |
|---|---|---|
event.httpMethod | string | "GET", "POST", "PUT", "DELETE", "PATCH", etc. |
event.path | string | URL path, e.g. "/users/42" |
event.headers | object | All request headers as key-value pairs (lowercase keys) |
event.multiValueHeaders | object | Same headers but each value is an array |
event.queryStringParameters | object|null | Query params as strings — ?a=1&a=2 gives a:"2" (last wins). null if no query. |
event.multiValueQueryStringParameters | object|null | Query params as arrays — ?a=1&a=2 gives a:["1","2"]. null if no query. |
event.body | string|null | Request body as a string. JSON/text = UTF-8. Binary = base64-encoded. null if empty. |
event.isBase64Encoded | boolean | true when body is base64 (binary uploads). false for JSON/text. |
event.rawBody | Buffer | Raw bytes of the request body. Use this for multipart/file parsing. |
event.requestContext.stage | string | "prod", "staging", or "local" |
event.requestContext.identity.sourceIp | string | Client IP address |
event.requestContext.identity.userAgent | string | Client User-Agent header |
Reading request data
export const handler = async (event) => {
const body = JSON.parse(event.body);
const page = event.queryStringParameters?.page ?? '1';
const match = event.path.match(/^\/users\/([a-z0-9]+)$/i);
const userId = match?.[1];
const ip = event.requestContext.identity.sourceIp;
};
The Response Object
Your handler must return a plain object. Only statusCode is required.
return {
statusCode: 200, // required — integer 100–599
headers: { // optional — single value per header
'Content-Type': 'application/json',
'Cache-Control': 'no-cache',
},
multiValueHeaders: { // optional — multiple values per header
'Set-Cookie': ['session=abc', 'theme=dark'],
},
body: JSON.stringify({ ok: true }), // string, object, or null
isBase64Encoded: false, // set true when sending binary files
};
Common response patterns
| Goal | Return value |
|---|---|
| JSON response | { statusCode: 200, headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ data }) } |
| Plain text | { statusCode: 200, headers: { 'Content-Type': 'text/plain' }, body: 'Hello' } |
| Send a file | { statusCode: 200, headers: { 'Content-Type': 'image/png', 'Content-Disposition': 'inline; filename="photo.png"' }, body: buffer.toString('base64'), isBase64Encoded: true } |
| Set cookies | { statusCode: 200, multiValueHeaders: { 'Set-Cookie': ['session=abc; HttpOnly', 'theme=dark; Max-Age=86400'] }, body: JSON.stringify({ ok: true }) } |
| Redirect | { statusCode: 302, headers: { Location: 'https://example.com' }, body: '' } |
| No content | { statusCode: 204, body: null } |
| Error | { statusCode: 400, headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ error: 'email is required' }) } |
init() — Configure Your Function
init() is optional. Export it to configure CORS and body handling at startup. If you don't export it, sensible defaults apply.
init() must be synchronous. No async, no await, no database connections — configuration only.
export const init = () => ({
cors: {
origins: ['*'], // which domains can call your function from a browser
credentials: false, // allow cookies and Authorization headers
},
body: {
json: true, // accept application/json (default: true)
urlencoded: false, // accept application/x-www-form-urlencoded
contentTypes: [], // additional MIME types — strings or RegExp
maxBytes: 10 * 1024 * 1024, // max body size (default 10MB, max 100MB)
},
});
Defaults when init() is not exported
| Setting | Default | Meaning |
|---|---|---|
cors.origins | ["*"] | Any domain can call your function |
cors.credentials | false | Cookies and auth headers not forwarded |
body.json | true | JSON requests accepted |
body.urlencoded | false | Form submissions rejected with 415 |
body.contentTypes | [] | No additional content types accepted |
body.maxBytes | 10 MB | Requests with larger bodies get 413 |
CORS Configuration (Node.js)
CORS controls which browser domains can call your function. It does not affect server-to-server calls (curl, Postman, backend services).
Allow any origin (default)
cors: { origins: ['*'] }
Restrict to specific domains
cors: {
origins: ['https://myapp.com', 'https://staging.myapp.com'],
}
Allow cookies and Authorization headers
cors: {
origins: ['https://myapp.com'], // must NOT be wildcard
credentials: true,
}
credentials: true with origins: ["*"] is blocked by policy. You must list specific domains.
Dynamic origins from environment variable
cors: { origins: (process.env.CORS_ORIGINS || '*').split(',') }
Set CORS_ORIGINS=https://myapp.com,https://staging.myapp.com in Function Configuration.
Body Handling (Node.js)
By default only JSON is accepted. Any other content type is rejected with 415 Unsupported Media Type before your handler is called.
Supported content types
| Content Type | MIME Type | How to enable |
|---|---|---|
| JSON | application/json | body.json: true (default) |
| Form submission | application/x-www-form-urlencoded | body.urlencoded: true |
| File upload | multipart/form-data | contentTypes: [/^multipart\/form-data/] |
| XML | text/xml | contentTypes: ["text/xml"] |
| Plain text | text/plain | contentTypes: ["text/plain"] |
| Images (all) | image/* | contentTypes: [/^image\//] |
application/pdf | contentTypes: ["application/pdf"] | |
| Binary | application/octet-stream | contentTypes: ["application/octet-stream"] |
Examples
JSON + form submissions
export const init = () => ({
body: { json: true, urlencoded: true },
});
export const handler = async (event) => {
const form = Object.fromEntries(new URLSearchParams(event.body));
// form.email, form.name, etc.
};
File uploads
export const init = () => ({
body: {
contentTypes: [/^multipart\/form-data/],
maxBytes: 50 * 1024 * 1024, // 50MB
},
});
// Use event.rawBody (not event.body) for multipart parsing
import Busboy from 'busboy';
import { Readable } from 'stream';
export const handler = async (event) => {
const busboy = Busboy({ headers: event.headers });
const readable = new Readable();
readable.push(event.rawBody);
readable.push(null);
readable.pipe(busboy);
};
Images
export const init = () => ({
body: { contentTypes: [/^image\//], maxBytes: 5 * 1024 * 1024 },
});
// event.isBase64Encoded is true, event.rawBody has raw bytes
How body is encoded per content type
| Content Type | event.body | event.isBase64Encoded |
|---|---|---|
application/json | UTF-8 string | false |
text/plain, text/xml | UTF-8 string | false |
x-www-form-urlencoded | UTF-8 string | false |
multipart/form-data | base64 string | true |
image/*, application/pdf | base64 string | true |
| No body (GET, DELETE) | null | false |
For binary content (multipart, images, PDF), always use event.rawBody instead of event.body.
Error Handling (Node.js)
What the platform handles automatically
| Condition | Status | Handler called? |
|---|---|---|
Body exceeds maxBytes | 413 | No |
| Content-Type not in allowed list | 415 | No |
| Handler returns wrong shape | 500 | Yes — but response was invalid |
| Handler throws an error | 500 | Yes — error logged, generic message sent to client |
Error messages from your handler are never sent to clients. Only generic messages are returned. Your errors are logged.
What you handle
export const handler = async (event) => {
try {
const body = JSON.parse(event.body);
if (!body.email) {
return { statusCode: 400, headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ error: 'email required' }) };
}
return { statusCode: 200, body: JSON.stringify({ ok: true }) };
} catch (err) {
console.error(err);
return { statusCode: 500, headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ error: 'Internal Server Error' }) };
}
};
init() Quick Reference (Node.js)
| Goal | init() config |
|---|---|
| Use defaults | Don't export init() — JSON + 10MB + open CORS |
| Lock CORS | cors: { origins: ['https://app.com'] } |
| Enable cookies | cors: { origins: ['https://app.com'], credentials: true } |
| Accept forms | body: { urlencoded: true } |
| File uploads | body: { contentTypes: [/^multipart\/form-data/], maxBytes: 50 * 1024 * 1024 } |
| XML API | body: { json: false, contentTypes: ['text/xml'] } |
| Images | body: { contentTypes: [/^image\//] } |
| Strict size limit | body: { maxBytes: 1024 * 1024 } — 413 on >1MB |