gRPC + REST Gateway parallel strategy
Boundary design that controls operational complexity while maintaining gRPC on the inside and REST on the outside

Introduction
To switch internal communication to gRPC while maintaining the existing REST API, contract management is more important than performance. Introducing a gateway ensures external compatibility, but if schema version and error code mapping is loose, debugging costs increase rapidly. This article summarizes the migration steps, team boundaries, and observation points for running REST and gRPC in parallel.

Problem definition
The reason the parallel strategy fails is because it treats the middle layer as temporary code. The following items must be fixed first:
- Proto changes and OpenAPI changes are separated, causing discrepancies between documentation and actual operation.
- Client failures increase as gRPC status codes are not consistently converted to HTTP status.
- There is no upstream metadata in the gateway log, so it is difficult to track delay time for each section.
In the previous stages, regression control takes precedence over transition speed. Contract testing and sample traffic replays should be included as mandatory procedures.
Key concepts
| perspective | Design criteria | Verification points |
|---|---|---|
| Contract Synchronization | Manage Proto from a single source of truth | Whether OpenAPI is automatically created |
| error mapping | Standardize bidirectional mapping between gRPC status and HTTP status | Client Retry Error Rate |
| Performance | split timeout budget at gateway | p95 latency increase/decrease |
| Observation | trace context propagation | Tracking the cause of delay by section |
The period of combining REST and gRPC is the period of highest architectural risk. The list of functional unit conversions and withdrawal criteria must be documented together.
Code example 1: gRPC status code HTTP mapping
import { status } from "@grpc/grpc-js";
export function grpcToHttp(code: status): number {
switch (code) {
case status.OK:
return 200;
case status.INVALID_ARGUMENT:
return 400;
case status.UNAUTHENTICATED:
return 401;
case status.PERMISSION_DENIED:
return 403;
case status.NOT_FOUND:
return 404;
case status.ALREADY_EXISTS:
return 409;
default:
return 500;
}
}
Code example 2: Gateway handler
export async function getUserProfile(req: Request) {
const id = new URL(req.url).searchParams.get("id");
if (!id) return Response.json({ message: "id required" }, { status: 400 });
try {
const result = await grpcClient.GetUser({ id }, { deadline: Date.now() + 300 });
return Response.json(result, { status: 200 });
} catch (error) {
const code = (error as { code?: number }).code;
return Response.json({ message: "upstream error" }, { status: grpcToHttp(code ?? 13) });
}
}
Architecture flow
Clean Architecture Layer Structure
Since the boundaries are likely to become blurred during the parallel period, it is safe to fix the gateway as an adapter layer based on Clean Architecture.
| Layer | responsibility | How to apply |
|---|---|---|
| Entities | request/response domain model | UserProfile, OrderSummary |
| Use Cases | Protocol independent business logic | GetUserProfileUseCase |
| Interface Adapters | REST, gRPC transformation | GatewayController, GrpcPresenter |
| Frameworks | Next.js Route/Env | HTTP server, gRPC client |
Infrastructure diagram
Tradeoffs
- The longer the parallel period, the higher the operating cost, but large-scale client compatibility can be maintained reliably.
- Automatic code generation is highly productive, but failure analysis becomes difficult if the translation layer is not understood.
- Adding a gateway cache reduces delay, but increases the complexity of the cache invalidation policy.
Cleanup
The gRPC transition is not a protocol replacement but a contract operation project. By placing the gateway as a clear adapter layer and matching code/documents/tests to the same source, stable step-by-step migration is possible.
Image source
- Cover: source link
- License: Public domain / Author: en:User:Pbroks13
- Note: After downloading the free license image from Wikimedia Commons, it was optimized to JPG at 1600px.