--- title: Node.js Reference --- # 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. :::info 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 ```js 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. ```js 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. :::warning `init()` must be synchronous. No `async`, no `await`, no database connections — configuration only. ::: ```js 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)** ```js cors: { origins: ['*'] } ``` **Restrict to specific domains** ```js cors: { origins: ['https://myapp.com', 'https://staging.myapp.com'], } ``` **Allow cookies and Authorization headers** ```js cors: { origins: ['https://myapp.com'], // must NOT be wildcard credentials: true, } ``` :::warning `credentials: true` with `origins: ["*"]` is blocked by policy. You must list specific domains. ::: **Dynamic origins from environment variable** ```js 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\//]` | | PDF | `application/pdf` | `contentTypes: ["application/pdf"]` | | Binary | `application/octet-stream` | `contentTypes: ["application/octet-stream"]` | ### Examples **JSON + form submissions** ```js 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** ```js 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** ```js 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` | :::tip 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 ```js 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 |