Meta Description: You don't have to choose just one API protocol. Learn how to support REST, GraphQL, and gRPC simultaneously with shared business logic.
Keywords: multi-protocol api, api gateway, protocol translation, rest graphql grpc, api architecture, polyglot api
Word Count: ~2,300 words
Your mobile team wants GraphQL for flexible queries. Your web team prefers REST for simplicity. Your microservices need gRPC for performance.
Do you build three separate APIs?
No. You build one API that supports multiple protocols. Each team gets the protocol they prefer, backed by the same business logic.
Here's how to do it.
Why Multi-Protocol?
Different Clients, Different Needs
Web browsers: REST works everywhere, caches well Mobile apps: GraphQL reduces bandwidth, fewer requests Microservices: gRPC offers performance, type safety IoT devices: REST is simple, widely supported
One protocol doesn't fit all use cases.
Gradual Migration
You can't switch protocols overnight. Multi-protocol support lets you: - Keep existing REST clients working - Introduce GraphQL for new features - Add gRPC for internal services - Migrate gradually without breaking changes
Developer Choice
Different developers prefer different tools. Multi-protocol APIs let teams use what they know best.
Architecture: Shared Business Logic
The key to multi-protocol APIs is separating protocol handling from business logic.
Layered Architecture
┌─────────────────────────────────────────┐
│ Protocol Layer │
│ ┌──────┐ ┌─────────┐ ┌──────┐ │
│ │ REST │ │ GraphQL │ │ gRPC │ │
│ └──┬───┘ └────┬────┘ └───┬──┘ │
└─────┼──────────┼───────────┼──────────┘
│ │ │
└──────────┴───────────┘
│
┌─────────────┴─────────────────────────┐
│ Business Logic Layer │
│ ┌──────────────────────────────┐ │
│ │ Service Layer │ │
│ │ - PetService │ │
│ │ - OrderService │ │
│ │ - UserService │ │
│ └──────────────────────────────┘ │
└───────────────────────────────────────┘
│
┌─────────────┴─────────────────────────┐
│ Data Layer │
│ ┌──────────────────────────────┐ │
│ │ Repositories │ │
│ │ - PetRepository │ │
│ │ - OrderRepository │ │
│ └──────────────────────────────┘ │
└───────────────────────────────────────┘
Protocol handlers translate requests to service calls. Services contain business logic. Repositories handle data access.
Example: Pet Service
// Business logic (protocol-agnostic)
class PetService {
constructor(private petRepository: PetRepository) {}
async getPet(id: string): Promise<Pet> {
const pet = await this.petRepository.findById(id);
if (!pet) {
throw new NotFoundError(`Pet ${id} not found`);
}
return pet;
}
async createPet(data: CreatePetInput): Promise<Pet> {
this.validatePetData(data);
return await this.petRepository.create(data);
}
async listPets(filters: PetFilters): Promise<Pet[]> {
return await this.petRepository.findMany(filters);
}
private validatePetData(data: CreatePetInput): void {
if (!data.name || data.name.length < 1) {
throw new ValidationError('Name is required');
}
if (!['DOG', 'CAT', 'BIRD', 'RABBIT'].includes(data.species)) {
throw new ValidationError('Invalid species');
}
}
}
This service works with any protocol. REST, GraphQL, and gRPC handlers call the same methods.
Implementing REST
REST handlers translate HTTP requests to service calls.
// REST controller
class PetController {
constructor(private petService: PetService) {}
async getPet(req: Request, res: Response) {
try {
const pet = await this.petService.getPet(req.params.id);
res.json(pet);
} catch (error) {
if (error instanceof NotFoundError) {
res.status(404).json({
type: 'https://petstoreapi.com/errors/not-found',
title: 'Not Found',
status: 404,
detail: error.message
});
} else {
res.status(500).json({
type: 'https://petstoreapi.com/errors/internal-error',
title: 'Internal Server Error',
status: 500
});
}
}
}
async createPet(req: Request, res: Response) {
try {
const pet = await this.petService.createPet(req.body);
res.status(201)
.location(`/v1/pets/${pet.id}`)
.json(pet);
} catch (error) {
if (error instanceof ValidationError) {
res.status(422).json({
type: 'https://petstoreapi.com/errors/validation-error',
title: 'Validation Error',
status: 422,
detail: error.message
});
} else {
res.status(500).json({
type: 'https://petstoreapi.com/errors/internal-error',
title: 'Internal Server Error',
status: 500
});
}
}
}
async listPets(req: Request, res: Response) {
const filters = {
species: req.query.species as string,
status: req.query.status as string,
limit: parseInt(req.query.limit as string) || 20
};
const pets = await this.petService.listPets(filters);
res.json({ data: pets });
}
}
// Routes
app.get('/v1/pets/:id', (req, res) => petController.getPet(req, res));
app.post('/v1/pets', (req, res) => petController.createPet(req, res));
app.get('/v1/pets', (req, res) => petController.listPets(req, res));
Implementing GraphQL
GraphQL resolvers call the same service methods.
// GraphQL schema
const typeDefs = `
type Pet {
id: ID!
name: String!
species: Species!
breed: String
age: Int
status: PetStatus!
}
enum Species {
DOG
CAT
BIRD
RABBIT
}
enum PetStatus {
AVAILABLE
PENDING
ADOPTED
}
type Query {
pet(id: ID!): Pet
pets(species: Species, status: PetStatus, limit: Int): [Pet!]!
}
type Mutation {
createPet(input: CreatePetInput!): Pet!
}
input CreatePetInput {
name: String!
species: Species!
breed: String
age: Int
}
`;
// GraphQL resolvers
const resolvers = {
Query: {
pet: async (_: any, { id }: { id: string }, context: Context) => {
try {
return await context.petService.getPet(id);
} catch (error) {
if (error instanceof NotFoundError) {
throw new GraphQLError('Pet not found', {
extensions: { code: 'NOT_FOUND' }
});
}
throw error;
}
},
pets: async (_: any, filters: PetFilters, context: Context) => {
return await context.petService.listPets(filters);
}
},
Mutation: {
createPet: async (_: any, { input }: { input: CreatePetInput }, context: Context) => {
try {
return await context.petService.createPet(input);
} catch (error) {
if (error instanceof ValidationError) {
throw new GraphQLError(error.message, {
extensions: { code: 'VALIDATION_ERROR' }
});
}
throw error;
}
}
}
};
Implementing gRPC
gRPC services call the same business logic.
// pets.proto
syntax = "proto3";
package petstore.v1;
service PetService {
rpc GetPet(GetPetRequest) returns (Pet);
rpc CreatePet(CreatePetRequest) returns (Pet);
rpc ListPets(ListPetsRequest) returns (ListPetsResponse);
}
message Pet {
string id = 1;
string name = 2;
Species species = 3;
string breed = 4;
int32 age = 5;
PetStatus status = 6;
}
enum Species {
SPECIES_UNSPECIFIED = 0;
SPECIES_DOG = 1;
SPECIES_CAT = 2;
SPECIES_BIRD = 3;
SPECIES_RABBIT = 4;
}
enum PetStatus {
PET_STATUS_UNSPECIFIED = 0;
PET_STATUS_AVAILABLE = 1;
PET_STATUS_PENDING = 2;
PET_STATUS_ADOPTED = 3;
}
message GetPetRequest {
string id = 1;
}
message CreatePetRequest {
string name = 1;
Species species = 2;
string breed = 3;
int32 age = 4;
}
message ListPetsRequest {
Species species = 1;
PetStatus status = 2;
int32 limit = 3;
}
message ListPetsResponse {
repeated Pet pets = 1;
}
```typescript // gRPC server implementation class PetServiceImpl implements IPetService { constructor(private petService: PetService) {}
async getPet(call: ServerUnaryCall, callback: sendUnaryData) { try { const pet = await this.petService.getPet(call.request.id); callback(null, this.toPr...[truncated 3577 chars]