Handling gRPC-Web vs REST in Axios HTTP Client
This article provides a technical comparison of executing gRPC-Web requests versus standard REST calls using the Axios HTTP client. While standard REST calls rely on standard JSON serialization and HTTP semantics, gRPC-Web introduces binary Protocol Buffers (Protobuf) encoding, custom framing headers, and trailer parsing. The following sections break down the differences in message formatting, transport configuration, and payload decoding required to handle gRPC-Web over Axios.
Core Protocol Differences
Standard REST requests over Axios use JSON payloads, typical HTTP methods (GET, POST, PUT, DELETE), and standard HTTP status codes for error handling.
gRPC-Web operates differently:
- HTTP Method: All gRPC-Web calls use
POST. - Content-Type: Uses
application/grpc-web(binary) orapplication/grpc-web-text(base64-encoded). - Framing: Every payload includes a 5-byte prefix containing compression flags (1 byte) and the message length (4 bytes big-endian).
- Status & Trailers: Status information (e.g.,
grpc-status,grpc-message) is transmitted via HTTP trailers or embedded in a final response frame rather than standard HTTP headers.
Configuring Axios for REST vs. gRPC-Web
1. Standard REST Request
Standard REST requests require minimal configuration. Axios handles JSON serialization and deserialization automatically.
import axios from 'axios';
async function fetchUserREST(userId) {
const response = await axios.get(`https://api.example.com/users/${userId}`, {
headers: {
'Accept': 'application/json',
},
});
return response.data; // Parsed JSON object
}2. gRPC-Web Request via Axios
To handle gRPC-Web manually in Axios, the request must use binary buffers for both sending and receiving, along with explicit framing and Protobuf serialization.
Request Construction:
- Serialize the message using compiled Protobuf definitions into a
Uint8Array. - Prepend the 5-byte gRPC frame header.
- Set
responseTypeto'arraybuffer'to receive raw binary data.
import axios from 'axios';
import { UserRequest, UserResponse } from './generated/user_pb';
function createGrpcFrame(payload) {
const frame = new Uint8Array(5 + payload.length);
// Byte 0: compression flag (0 = uncompressed)
frame[0] = 0;
// Bytes 1-4: big-endian message length
const view = new DataView(frame.buffer);
view.setUint32(1, payload.length, false);
// Remaining bytes: serialized protobuf message
frame.set(payload, 5);
return frame;
}
async function fetchUserGrpc(userId) {
const req = new UserRequest();
req.setId(userId);
const serialized = req.serializeBinary();
const framedPayload = createGrpcFrame(serialized);
const response = await axios.post('https://api.example.com/UserService/GetUser', framedPayload, {
headers: {
'Content-Type': 'application/grpc-web+proto',
'X-Grpc-Web': '1',
},
responseType: 'arraybuffer',
});
return parseGrpcResponse(new Uint8Array(response.data));
}Parsing gRPC-Web Responses:
The response data contains one or more 5-byte framed messages
followed by a trailer frame (indicated by a 0x80 flag in
byte 0).
function parseGrpcResponse(buffer) {
let offset = 0;
let responseMessage = null;
while (offset < buffer.length) {
const flag = buffer[offset];
const view = new DataView(buffer.buffer, buffer.byteOffset + offset);
const length = view.getUint32(1, false);
offset += 5;
const chunk = buffer.subarray(offset, offset + length);
offset += length;
if (flag === 0x00) {
// Data frame
responseMessage = UserResponse.deserializeBinary(chunk);
} else if (flag === 0x80) {
// Trailer frame (contains status headers as raw text)
const trailerText = new TextDecoder().decode(chunk);
// Process grpc-status and grpc-message from trailerText
}
}
return responseMessage;
}Key Comparison Summary
| Feature | Axios REST Call | Axios gRPC-Web Call |
|---|---|---|
| HTTP Method | GET, POST, PUT, DELETE, etc. | POST only |
| Content-Type | application/json |
application/grpc-web+proto or
application/grpc-web-text |
| Payload Format | Plain JSON or Text | Binary Protobuf with 5-byte frame prefix |
| Response Type | json (default) |
arraybuffer or
text |
| Error Handling | HTTP status codes (4xx, 5xx) | grpc-status
headers/trailers |
| Streaming | Limited (chunked transfer) | Supported for Server Streaming over single connection |
Practical Recommendation
While Axios can send and receive gRPC-Web frames using custom buffer
manipulation, direct implementation requires writing boilerplate for
framing, trailer parsing, and metadata handling. For production
environments, using dedicated clients such as
@grpc/grpc-web or leveraging custom Axios transport
adapters built for gRPC is standard practice.