Learn/Advanced Topics

JSON in Serverless & Edge Computing

Every serverless function receives a JSON event and returns a JSON response. But cold starts, memory limits, payload size caps, and edge runtime restrictions mean JSON handling must be optimized differently than on traditional servers. This guide covers AWS Lambda, Cloudflare Workers, and Vercel Edge Functions.

JSON as the Serverless Interface

JSON Flow in Serverless Architecture
PlatformRuntimeMemory LimitResponse Size LimitMax Duration
AWS LambdaNode.js, Python, Go, Java, .NET128 MB - 10 GB6 MB (sync)15 minutes
Cloudflare WorkersV8 isolate (JavaScript)128 MB (paid)~100 MB30 sec CPU / 15 min wall
Vercel Edge FunctionsV8 isolate (Edge Runtime)128 MB4 MB30 seconds
Vercel ServerlessNode.js (full)1024 MB default4.5 MB60 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 safely
3 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 }
16
17 try {
18 const result = await processOrder(body);
19
20 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 }));
35
36 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 uploaded
2export 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};
11
12// SQS event: each record's body is a JSON string
13export 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 failure
20 return {
21 batchItemFailures: results
22 .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);
4
5 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 }
15
16 // Store in KV
17 await env.DATA_KV.put(
18 `item:${body.id}`,
19 JSON.stringify({ ...body, updated_at: new Date().toISOString() }),
20 { expirationTtl: 86400 } // 24 hours
21 );
22
23 return Response.json(
24 { success: true, id: body.id },
25 { status: 201 }
26 );
27 }
28
29 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}`);
32
33 if (!raw) {
34 return Response.json(
35 { error: 'Not found' },
36 { status: 404 }
37 );
38 }
39
40 return Response.json(JSON.parse(raw));
41 }
42
43 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';
2
3export const config = { runtime: 'edge' };
4
5export default async function handler(req: NextRequest) {
6 if (req.method === 'GET') {
7 // Simple JSON response
8 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 }
14
15 if (req.method === 'POST') {
16 const body = await req.json();
17
18 // Validate
19 if (!body.query) {
20 return Response.json(
21 { error: 'query field is required' },
22 { status: 400 }
23 );
24 }
25
26 // Stream a large JSON response
27 const stream = new ReadableStream({
28 async start(controller) {
29 const encoder = new TextEncoder();
30 controller.enqueue(encoder.encode('['));
31
32 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 }
37
38 controller.enqueue(encoder.encode(']'));
39 controller.close();
40 },
41 });
42
43 return new Response(stream, {
44 headers: { 'Content-Type': 'application/json' },
45 });
46 }
47}

Optimizing JSON for Serverless

Payload Size Reduction

TechniqueSavingsWhen to Use
Short field names10-30%Internal service-to-service events (not public APIs)
Remove null fields5-15%When nulls carry no semantic meaning
Use arrays instead of objects for tabular data20-40%Large datasets with repeated keys
Gzip/Brotli compression60-80%API Gateway and Workers support it natively
PaginationUnboundedAny list endpoint to stay under size limits

Cold Start Mitigation

Minimize cold start JSON overheadjavascript
1// SLOW: Importing a large JSON config at module level
2// This is parsed on every cold start
3import config from './large-config.json'; // avoid if > 100KB
4
5// FASTER: Lazy-load large JSON only when needed
6let _config = null;
7function getConfig() {
8 if (!_config) {
9 _config = require('./large-config.json');
10 }
11 return _config;
12}
13
14// FASTEST: Store config in environment variable or KV
15// Environment variables are available without file I/O
16const 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}
15
16// Usage in Lambda
17if (!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 manual JSON.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 ReadableStream for large JSON responses on edge platforms
  • Don't import large JSON files at module scope — it slows every cold start
  • Don't assume event.body is 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

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.