Building Multi-Protocol API Architecture

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

TRY NANO BANANA FOR FREE

Building Multi-Protocol API Architecture

TRY NANO BANANA FOR FREE
Contents

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]