Node.js ESM Reference
This reference covers the full contract for Node.js Http functions (Node 20, 22, 24). All three runtimes share the same event, response, and configuration interface — code written for one works on any of them without changes.
This guide 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
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
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
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' }) };
}
// your logic here
return { statusCode: 200, body: JSON.stringify({ ok: true }) };
} catch (err) {
console.error(err); // logged for your team
return { statusCode: 500, headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ error: 'Internal Server Error' }) };
}
};
init() Quick Reference
| 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 |