JSON as the Serverless Interface
| Platform | Runtime | Memory Limit | Response Size Limit | Max Duration |
|---|---|---|---|---|
| AWS Lambda | Node.js, Python, Go, Java, .NET | 128 MB - 10 GB | 6 MB (sync) | 15 minutes |
| Cloudflare Workers | V8 isolate (JavaScript) | 128 MB (paid) | ~100 MB | 30 sec CPU / 15 min wall |
| Vercel Edge Functions | V8 isolate (Edge Runtime) | 128 MB | 4 MB | 30 seconds |
| Vercel Serverless | Node.js (full) | 1024 MB default | 4.5 MB | 60 seconds (pro) |
AWS Lambda Event Payloads
API Gateway v2 (HTTP API)
API Gateway v2 event structurejson
1{2 "version": "2.0",3 "routeKey": "POST /api/orders",4 "rawPath": "/api/orders",5 "headers": {6 "content-type": "application/json",7 "authorization": "Bearer eyJhbGci..."8 },9 "queryStringParameters": {10 "page": "1",11 "limit": "20"12 },13 "body": "{"product_id":"prod-001","quantity":2}",14 "isBase64Encoded": false,15 "requestContext": {16 "http": {17 "method": "POST",18 "path": "/api/orders",19 "sourceIp": "203.0.113.1"20 },21 "requestId": "req_abc123",22 "time": "15/Apr/2026:14:30:01 +0000"23 }24}Note
The
body field is a string, not a parsed object. You must call JSON.parse(event.body) to access the request data. Always wrap this in a try-catch to handle malformed JSON from clients.Lambda Handler Pattern
Robust Lambda JSON handlerjavascript
1export const handler = async (event) => {2 // Parse the request body safely3 let body;4 try {5 body = event.body ? JSON.parse(event.body) : {};6 } catch {7 return {8 statusCode: 400,9 headers: { 'Content-Type': 'application/json' },10 body: JSON.stringify({11 error: 'Invalid JSON in request body',12 request_id: event.requestContext?.requestId,13 }),14 };15 }1617 try {18 const result = await processOrder(body);1920 return {21 statusCode: 201,22 headers: { 'Content-Type': 'application/json' },23 body: JSON.stringify({24 data: result,25 request_id: event.requestContext?.requestId,26 }),27 };28 } catch (err) {29 console.error(JSON.stringify({30 level: 'error',31 message: 'Order processing failed',32 error: err.message,33 request_id: event.requestContext?.requestId,34 }));3536 return {37 statusCode: 500,38 headers: { 'Content-Type': 'application/json' },39 body: JSON.stringify({40 error: 'Internal server error',41 request_id: event.requestContext?.requestId,42 }),43 };44 }45};S3 and SQS Event Payloads
Processing S3 and SQS JSON eventsjavascript
1// S3 event: triggered when a JSON file is uploaded2export const s3Handler = async (event) => {3 for (const record of event.Records) {4 const bucket = record.s3.bucket.name;5 const key = decodeURIComponent(record.s3.object.key);6 console.log(JSON.stringify({7 level: 'info', message: 'Processing S3 object', bucket, key,8 }));9 }10};1112// SQS event: each record's body is a JSON string13export const sqsHandler = async (event) => {14 const results = [];15 for (const record of event.Records) {16 const message = JSON.parse(record.body);17 results.push(await processMessage(message));18 }19 // Return failed message IDs for partial batch failure20 return {21 batchItemFailures: results22 .filter(r => !r.success)23 .map(r => ({ itemIdentifier: r.messageId })),24 };25};Cloudflare Workers
Cloudflare Worker JSON APIjavascript
1export default {2 async fetch(request, env) {3 const url = new URL(request.url);45 if (request.method === 'POST' && url.pathname === '/api/data') {6 let body;7 try {8 body = await request.json();9 } catch {10 return Response.json(11 { error: 'Invalid JSON' },12 { status: 400 }13 );14 }1516 // Store in KV17 await env.DATA_KV.put(18 `item:${body.id}`,19 JSON.stringify({ ...body, updated_at: new Date().toISOString() }),20 { expirationTtl: 86400 } // 24 hours21 );2223 return Response.json(24 { success: true, id: body.id },25 { status: 201 }26 );27 }2829 if (request.method === 'GET' && url.pathname.startsWith('/api/data/')) {30 const id = url.pathname.split('/').pop();31 const raw = await env.DATA_KV.get(`item:${id}`);3233 if (!raw) {34 return Response.json(35 { error: 'Not found' },36 { status: 404 }37 );38 }3940 return Response.json(JSON.parse(raw));41 }4243 return Response.json({ error: 'Not found' }, { status: 404 });44 },45};Response.json()
Response.json(data, init) is a convenience method that stringifies the data and sets Content-Type: application/json automatically. It is available in Cloudflare Workers, Vercel Edge Functions, and Deno. Prefer it over manual new Response(JSON.stringify(data)).Vercel Edge Functions
Vercel Edge Function with streaming JSONtypescript
1import { NextRequest } from 'next/server';23export const config = { runtime: 'edge' };45export default async function handler(req: NextRequest) {6 if (req.method === 'GET') {7 // Simple JSON response8 return Response.json({9 message: 'Hello from the edge',10 region: req.headers.get('x-vercel-id')?.split('::')[0],11 timestamp: new Date().toISOString(),12 });13 }1415 if (req.method === 'POST') {16 const body = await req.json();1718 // Validate19 if (!body.query) {20 return Response.json(21 { error: 'query field is required' },22 { status: 400 }23 );24 }2526 // Stream a large JSON response27 const stream = new ReadableStream({28 async start(controller) {29 const encoder = new TextEncoder();30 controller.enqueue(encoder.encode('['));3132 const results = await fetchResults(body.query);33 for (let i = 0; i < results.length; i++) {34 if (i > 0) controller.enqueue(encoder.encode(','));35 controller.enqueue(encoder.encode(JSON.stringify(results[i])));36 }3738 controller.enqueue(encoder.encode(']'));39 controller.close();40 },41 });4243 return new Response(stream, {44 headers: { 'Content-Type': 'application/json' },45 });46 }47}Optimizing JSON for Serverless
Payload Size Reduction
| Technique | Savings | When to Use |
|---|---|---|
| Short field names | 10-30% | Internal service-to-service events (not public APIs) |
| Remove null fields | 5-15% | When nulls carry no semantic meaning |
| Use arrays instead of objects for tabular data | 20-40% | Large datasets with repeated keys |
| Gzip/Brotli compression | 60-80% | API Gateway and Workers support it natively |
| Pagination | Unbounded | Any list endpoint to stay under size limits |
Cold Start Mitigation
Minimize cold start JSON overheadjavascript
1// SLOW: Importing a large JSON config at module level2// This is parsed on every cold start3import config from './large-config.json'; // avoid if > 100KB45// FASTER: Lazy-load large JSON only when needed6let _config = null;7function getConfig() {8 if (!_config) {9 _config = require('./large-config.json');10 }11 return _config;12}1314// FASTEST: Store config in environment variable or KV15// Environment variables are available without file I/O16const config = JSON.parse(process.env.APP_CONFIG || '{}');Structured Error Responses
Consistent JSON error format across serverless functionsjavascript
1function errorResponse(statusCode, message, details = {}) {2 return {3 statusCode,4 headers: { 'Content-Type': 'application/json' },5 body: JSON.stringify({6 error: {7 code: statusCode,8 message,9 ...details,10 timestamp: new Date().toISOString(),11 },12 }),13 };14}1516// Usage in Lambda17if (!event.body) return errorResponse(400, 'Request body is required');18if (!body.email) return errorResponse(422, 'Validation failed', {19 fields: [{ field: 'email', message: 'Email is required' }],20});Best Practices
- ✓Always wrap
JSON.parse(event.body)in try-catch — clients send malformed JSON - ✓Use
Response.json()instead of manualJSON.stringify+new Response - ✓Emit structured JSON logs — CloudWatch and Workers logs are indexed automatically
- ✓Use pagination to keep responses under platform size limits
- ✓Enable Gzip compression at the API Gateway or CDN level
- ✓Use
ReadableStreamfor large JSON responses on edge platforms - ✗Don't import large JSON files at module scope — it slows every cold start
- ✗Don't assume
event.bodyis a parsed object — it is a string in Lambda - ✗Don't use Node.js-specific APIs (fs, streams) in edge runtimes — use Web APIs instead
Try These Tools
Continue Learning
Frequently Asked Questions
What JSON does a Lambda function receive?
Every Lambda invocation receives a JSON event object. The structure depends on the trigger: API Gateway sends HTTP request details (method, headers, body, path parameters), S3 sends bucket/key information for object events, SQS sends message records, and CloudWatch Events sends scheduled event data. The handler signature is handler(event, context) where event is the parsed JSON object.
What is Lambda's response size limit?
AWS Lambda has a 6 MB response payload limit for synchronous invocations (API Gateway, direct invoke) and a 256 KB limit for asynchronous invocations. If your JSON response exceeds these limits, consider pagination, writing large results to S3 and returning a pre-signed URL, or streaming responses with Lambda response streaming (available for Node.js 18+ runtime).
Can I use JSON.parse in Cloudflare Workers?
Yes. Cloudflare Workers run on V8 isolates and support standard JSON.parse and JSON.stringify. However, the Workers runtime has a 128 MB memory limit (paid plan) and a CPU time limit of 30 seconds. For large JSON payloads, consider using the Streams API to process data incrementally instead of parsing the entire payload into memory.
How do I stream JSON from Vercel Edge Functions?
Vercel Edge Functions support the Web Streams API. Return a Response with a ReadableStream body to stream JSON incrementally. This is useful for large datasets or real-time data. Use TransformStream to construct JSON arrays where each element is flushed to the client as it becomes available.
Do cold starts affect JSON parsing performance?
Yes. During a cold start, the JavaScript engine must initialize and the V8 JIT compiler has not yet optimized JSON.parse for your payload patterns. The first JSON.parse call on a cold start can be 2-5x slower than subsequent calls. Mitigations: keep payloads small, use provisioned concurrency (Lambda), or choose lightweight runtimes like LLRT or Bun.
How should I store JSON in Cloudflare KV?
Cloudflare KV stores values as strings with a 25 MB size limit. Store JSON by stringifying it: await NAMESPACE.put("key", JSON.stringify(data)). Retrieve and parse: JSON.parse(await NAMESPACE.get("key")). For structured data that needs querying, consider Cloudflare D1 (SQLite) or Durable Objects instead of KV.
What is the difference between API Gateway v1 and v2 event formats?
API Gateway REST API (v1) wraps the request in a verbose structure with multiValueHeaders and multiValueQueryStringParameters. API Gateway HTTP API (v2) uses a simpler, flatter format with a requestContext object containing HTTP details and a cookies array. V2 events are about 50% smaller. Use v2 unless you need REST API-specific features like request validation or API keys.