Prompt and scope
You must process binary request bodies in a browser or edge runtime. The platform adds Request.bytes(), but a request body can be consumed only once. Explain when to use it, how to avoid double reads, how to cap memory, and how to design a fallback.
MDN describes Request.bytes() as returning a Promise whose fulfilled value is a Uint8Array of the request body; reading a body consumes it. The interview tests Fetch body lifetime, resource limits, and errors rather than treating the new method as a universal default.
What the interviewer evaluates
- Explaining the used-body state and the mutual exclusivity of
bytes(),arrayBuffer(),json(), andtext(). - Choosing complete versus streaming reads from request size and processing goals.
- Placing one read boundary among retries, logging, signature verification, and business parsing.
- Designing capability detection, limits, cancellation, timeouts, and error mapping.
- Preventing raw bodies from entering logs, caches, or cross-tenant objects.
Clarifying questions
- What are body size, source, Content-Type, signature requirements, and target runtime?
- Does the business need a complete byte array, incremental hashing, chunk upload, or decoded data?
- Which layer retries, and is replay safe?
- Can the body contain personal data, credentials, or tenant-isolation data?
Body lifetime and API choice
After Request.bytes() consumes a body successfully, later json() or text() calls fail, and the reverse is also true. If several consumers need the data, read once at a single boundary and pass controlled results to parsing, verification, and business code. Do not pass a Request object through many modules that compete to read it.
Complete reads fit bounded small requests or protocols requiring one-shot validation. Large requests should prefer request.body streaming, performing incremental hashing and size checks. A Uint8Array result is convenient for byte processing but does not remove the memory peak of a complete read.
Memory, limits, and backpressure
Check Content-Length at the edge, but do not trust it alone; with chunked transfer, count actual bytes and abort when the limit is exceeded. Set tenant, route, and global caps for complete reads so concurrent requests cannot allocate unbounded arrays. Streaming paths apply backpressure when consumers fall behind.
For forwarding, do not copy multiple full arrays for logging or retry. If replay is required, write size-limited bytes to controlled temporary storage with tenant binding, expiry, and a digest; raw bodies do not belong in ordinary logs.
Signature verification, parsing, and errors
Before verification, define whether the signature covers raw bytes or a canonical structure. Feed the raw bytes from the one read to both verifier and parser; JSON reserialization can change whitespace, order, or encoding. Map parse failure, signature failure, size violation, cancellation, and timeout to distinct business errors instead of one “malformed” response.
Edge functions may expose different body APIs. Detect capability and choose bytes(), arrayBuffer(), or streaming, recording the runtime capability version at startup. Fallbacks must preserve limits, digest input, and error semantics; an old runtime must not silently weaken security.
Cancellation, timeouts, and replay
Pass an AbortSignal to reads and downstream operations. Stop reading and release references when the client disconnects or a deadline expires. Never automatically replay a side-effecting request after timeout without an idempotency key, server deduplication, and an explicit retry window. Even safe-looking queries need a replay-leak review.
Gateway retries can create request copies, so signature verification, idempotency keys, and audit events should share one request ID. An error response should state whether retry is safe without exposing internal read state or key material.
Security and observability
Limit Content-Type, body size, read duration, and concurrency. Account for decompressed size to resist decompression bombs. Isolate caches and temporary objects by tenant; keep sensitive bytes only as long as needed. Record byte count, duration, cancellation reason, error class, and request digest, never content.
Monitor body-consumption failures, limit violations, p95 read time, peak memory, downstream backpressure, and retries. If bytes() failures rise in one runtime, switch to a tested fallback and alert; catching an exception and reading the same body again is not recovery.
Validation checklist and follow-ups
Test empty, one-byte, near-limit, over-limit, chunked, slow-client, disconnect, double-read, signature mismatch, decompression-bomb, concurrent, old-runtime fallback, and downstream-timeout cases. Verify every path consumes the body exactly once and that cancellation stops further reads.
Why not call json() and then use bytes() for verification?
The first read has consumed the body, and parsing plus reserialization can change whitespace, order, or encoding. Read raw bytes once and feed the same bytes to verification and parsing.
When is bytes() preferable to streaming?
Use it for small bounded requests when the protocol requires complete-byte validation. Large files, continuous uploads, and high concurrency need streaming and incremental processing.
How do you prove the fallback is equally safe?
Force each capability path in every runtime and compare limits, digests, signatures, errors, cancellation, and audit events. The fallback must not change security policy or retry boundaries.