## Documentation Index

Fetch the complete documentation index at: [/llms.txt](https://cosmo-docs.wundergraph.com/llms.txt)

Use this file to discover all available pages before exploring further.

This tutorial is part of [Cosmo Connect](https://cosmo-docs.wundergraph.com/connect/overview) and focuses on deploying a [Router Plugin](https://cosmo-docs.wundergraph.com/router/gRPC/plugins).

## Prerequisites

- [Cosmo CLI](https://cosmo-docs.wundergraph.com/cli/intro) (minimum version: [0.98.0](https://github.com/wundergraph/cosmo/releases/tag/wgc%400.98.0)) installed and configured
- [Cosmo Router](https://cosmo-docs.wundergraph.com/router/intro) (minimum version: [0.242.0](https://github.com/wundergraph/cosmo/releases/tag/router%400.242.0)) installed and configured to use Cosmo Cloud

If you’re new to Cosmo, you should start with the [Cosmo Cloud Onboarding](https://cosmo-docs.wundergraph.com/getting-started/cosmo-cloud-onboarding) guide. This tutorial assumes you’ve created a federated graph and deployed and configured router(s) for it.

## Overview

gRPC plugins are a powerful new way to write and deploy subgraphs without the need for a GraphQL server. You can use plugins to wrap legacy APIs, mock future subgraphs or fully implement new features. Plugins are written in Go or TypeScript and can be deployed and run automatically by the Cosmo Router.

For this tutorial, we’ll create a gRPC plugin called `starwars` that wraps a small portion of the REST API from [SWAPI](https://swapi.info/). SWAPI is a free and open-source public API that provides information about Star Wars characters, planets, and more. For the tutorial, it functions as a stand-in for your own API or external datasource (an SQL database, Stripe, etc.).

gRPC plugins support all the same features as [gRPC services](https://cosmo-docs.wundergraph.com/router/gRPC/grpc-services), but you don’t have to create separate deployments, handle networking, or manage inter-service authentication.

Here’s a short version of the steps for reference:

1. Initialize a new plugin
   Create a new gRPC plugin using `wgc router plugin init <name>` and define your GraphQL schema.

2. Design your schema
   Define your GraphQL schema that will be used to integrate your plugin into your federated graph.

3. Generate & Implement
   Generate Protobuf code with `wgc router plugin generate` and implement your resolvers in Go.

4. Publish & Deploy
   Publish your plugin to Cosmo with `wgc router plugin publish` and test via GraphQL queries.

## Setting up the CLI

Before we start, we should make sure that your version of our CLI `wgc` is up to date and you’re logged in. Check your version with:

```
wgc --version
```

This needs to be `>=0.90.1` for the tutorial to work correctly.

Log in with:

```
wgc auth login
```

After logging in, verify your session and ensure you’re in the correct organization by running:

```
wgc auth whoami
```

## Building Plugins

First off, let’s get familiar with some terminology we use to describe different parts of the platform:

- **Cosmo Cloud**: Our cloud-hosted platform for publishing and monitoring your subgraphs
- **Supergraph**: The unified graph containing the composition of all your subgraphs
- **Subgraph**: A small part of a supergraph, usually serving a small service contract. For example, “billing”
- **gRPC Subgraph**: A subgraph built with our new gRPC-based tooling that can be run standalone or as a gRPC plugin
- **gRPC Plugin**: A way to run subgraphs created with gRPC alongside the router directly in your infrastructure, no extra deployment required

Now that we have that sorted, let’s move onto the good stuff.

### Initialize Plugin

You can create a gRPC plugin using our CLI tool, `wgc` (Replace `preferredLang` with either `go` for Go, and `ts` for TypeScript):

```
wgc router plugin init starwars --language <preferredLang>
```

You should now have a directory containing a single plugin, `starwars`. We’ll go over what each file does soon.

#### Go Directory Structure:
```
starwars
├── Dockerfile
├── generated
│   ├── mapping.json
│   ├── service.proto
│   └── service.proto.lock.json
├── go.mod
├── Makefile
├── README.md
└── src
    ├── main.go
    ├── main_test.go
    └── schema.graphql
```

#### TypeScript Directory Structure:
```
starwars
├── Dockerfile
├── generated
│   ├── mapping.json
│   ├── service.proto
│   └── service.proto.lock.json
├── package.json
├── tsconfig.json
├── Makefile
├── README.md
├── patches
└── src
  ├── plugin.ts
  ├── plugin.test.ts
  ├── plugin-server.ts
  └── schema.graphql
```

### Design your schema

The first thing we need to do is take a look at the GraphQL schema in `starwars/src/schema.graphql`. It doesn’t contain our schema yet, so we’ll start by defining our new service in GraphQL terms. When you open this file, it will have some placeholder schema inside. You can safely remove all of the content and replace it with the following:

```graphql
type Person {
  """
  The name of this person
  """
  name: String!

"""
  The height of the person in centimeters
  """
  height: String!

"""
  The mass of the person in kilograms
  """
  mass: String!

"""
  The hair color of this person. Will be "unknown" if not known or "n/a" if the person does not have hair
  """
  hair_color: String!

"""
  The skin color of this person
  """
  skin_color: String!

"""
  The eye color of this person. Will be "unknown" if not known or "n/a" if the person does not have an eye
  """
  eye_color: String!

"""
  The birth year of the person, using BBY or ABY (Before/After Battle of Yavin)
  """
  birth_year: String!

"""
  The gender of this person. Either "Male", "Female" or "unknown", "n/a" if no gender
  """
  gender: String!
}

type Query {
  """
  get all the people
  """
  people: [Person!]!
}
```

For this example, we won’t utilize the entire SWAPI for this tutorial, only the `people` resource and its endpoints. This schema has a single type, “Person”, and a query to get all the people or a specific person by ID.

### Generate and Implement

Now we can use `wgc` again to generate the Protobuf representation of our subgraph and boilerplate code to implement it in Golang.

```
wgc router plugin generate ./starwars
```

You’ll now see a few new files in the `generated` folder. Pick the appropriate tab based on the language you wish to follow the tutorial in.

### Publish and Deploy

Now, our plugin is in a semi-working state, and we can publish it to test it out as part of our federated graph. Using our CLI `wgc`, publish the plugin:

```
wgc router plugin publish ./starwars
```

How does deployment work?

When you run the deploy command, your plugin is built and packaged using the `Dockerfile` in the plugin directory. We then send this image containing your plugin and any other files it may need off to our Cosmo Cloud Registry where it can be pulled by your routers. Importantly, the router does not run plugins using Docker or in a container, instead we unpack the final target of the image into a working directory and run the plugin directly. This means if your plugin depends on a system dependency, it must be present in your Router image, not the plugin image.

Congratulations! You’ve created your first gRPC plugin. You’ll see the output from a Docker build and push, followed by a successful completion. If you have a router deployed serving your federated graph, you can now query `people` via GraphQL.

### Updating the plugin

You can update the schema or the implementation of the plugin by modifying the `schema.graphql` file or the `main.go` file, respectively. If you update the schema, you need to regenerate the code by running `wgc router plugin generate`.

## Appendix

Full plugin code

### src/schema.graphql
```graphql
type Person {
  """
  The name of this person
  """
  name: String!

"""
  The height of the person in centimeters
  """
  height: String!

"""
  The mass of the person in kilograms
  """
  mass: String!

"""
  The hair color of this person. Will be "unknown" if not known or "n/a" if the person does not have hair
  """
  hair_color: String!

"""
  The skin color of this person
  """
  skin_color: String!

"""
  The eye color of this person. Will be "unknown" if not known or "n/a" if the person does not have an eye
  """
  eye_color: String!

"""
  The birth year of the person, using BBY or ABY (Before/After Battle of Yavin)
  """
  birth_year: String!

"""
  The gender of this person. Either "Male", "Female" or "unknown", "n/a" if no gender
  """
  gender: String!
}

type Query {
  """
  get all the people
  """
  people: [Person!]!
}
```

### src/main.go
```go
package main

import (
  "context"
  "log"
  "net/http"
  "time"

service "github.com/wundergraph/cosmo/plugin/generated"
  "github.com/wundergraph/cosmo/router-plugin/httpclient"

routerplugin "github.com/wundergraph/cosmo/router-plugin"
  "google.golang.org/grpc"
  "google.golang.org/grpc/codes"
  "google.golang.org/grpc/status"
)

type SWAPIPerson struct {
  Name      string `json:"name"`
  Height    string `json:"height"`
  Mass      string `json:"mass"`
  HairColor string `json:"hair_color"`
  SkinColor string `json:"skin_color"`
  EyeColor  string `json:"eye_color"`
  BirthYear string `json:"birth_year"`
  Gender    string `json:"gender"`
}

func main() {
  // 1. Initialize the HTTP client
  client := httpclient.New(
    httpclient.WithBaseURL("https://swapi.info/api/"),
    httpclient.WithTimeout(30*time.Second),
    httpclient.WithHeader("Accept", "application/json"),
  )

// 2. Add the new HTTP client to the service
  pl, err := routerplugin.NewRouterPlugin(func(s *grpc.Server) {
    s.RegisterService(&service.StarwarsService_ServiceDesc, &StarwarsService{
      client: client,
    })
  })

if err != nil {
    log.Fatalf("failed to create router plugin: %v", err)
  }

pl.Serve()
}

type StarwarsService struct {
  service.UnimplementedStarwarsServiceServer
  client *httpclient.Client
}

func (s *StarwarsService) QueryPeople(ctx context.Context, req *service.QueryPeopleRequest) (*service.QueryPeopleResponse, error) {
  // 1. Send the request and handle the response
  resp, err := s.client.Get(ctx, "/people")
  if err != nil {
    return nil, status.Errorf(codes.Internal, "failed to fetch people: %v", err)
  }

// 2. If the response status is not OK, return an error
  if resp.StatusCode != http.StatusOK {
    return nil, status.Errorf(codes.Internal, "failed to fetch people: SWAPI returned status %d", resp.StatusCode)
  }

// 3. Read out the response body into a list of SWAPIPerson
  people, err := httpclient.UnmarshalTo[[]SWAPIPerson](resp)
  if err != nil {
    return nil, status.Errorf(codes.Internal, "failed to decode response: %v", err)
  }

// 4. Convert the []SWAPIPerson to []*service.Person (the type needed for our RPC's return type)
  protoPeople := make([]*service.Person, len(people)) 
  for i, person := range people {
    protoPeople[i] = &service.Person{
      Name:      person.Name,
      Height:    person.Height,
      Mass:      person.Mass,
      HairColor: person.HairColor,
      SkinColor: person.SkinColor,
      EyeColor:  person.EyeColor,
      BirthYear: person.BirthYear,
      Gender:    person.Gender,
    }
  }

// 5. Return a response containing the converted people objects
  return &service.QueryPeopleResponse{
    People: protoPeople,
  }, nil
}
```

### src/plugin.ts
```typescript
import * as grpc from '@grpc/grpc-js';
import axios, { type AxiosInstance } from "axios";
import {
  StarwarsServiceService,
  IStarwarsServiceServer
} from '../generated/service_grpc_pb.js';
import {
  QueryPeopleRequest,
  QueryPeopleResponse,
  Person
} from '../generated/service_pb.js';
import { PluginServer } from './plugin-server.js';

interface SWAPIPerson {
  name: string;
  height: string;
  mass: string;
  hair_color: string;
  skin_color: string;
  eye_color: string;
  birth_year: string;
  gender: string;
}

export class StarwarsService implements IStarwarsServiceServer {
  [name: string]: grpc.UntypedHandleCall;

#starWarsClient: AxiosInstance;

constructor() {
    this.#starWarsClient = axios.create({
      baseURL: "https://swapi.info/api/",
      timeout: 30_000,
      headers: { Accept: "application/json" },
    });
  }

queryPeople(
      call: grpc.ServerUnaryCall<QueryPeopleRequest, QueryPeopleResponse>,
      callback: grpc.sendUnaryData<QueryPeopleResponse>
  ) {
      this.#starWarsClient.get<SWAPIPerson[]>("/people")
          .then((resp) => {
            const people = resp.data;

const protoPeople = people.map((person) => {
              const protoPerson = new Person();
              protoPerson.setName(person.name);
              protoPerson.setHeight(person.height);
              protoPerson.setMass(person.mass);
              protoPerson.setHairColor(person.hair_color);
              protoPerson.setSkinColor(person.skin_color);
              protoPerson.setEyeColor(person.eye_color);
              protoPerson.setBirthYear(person.birth_year);
              protoPerson.setGender(person.gender);
              return protoPerson;
            });

const response = new QueryPeopleResponse();
            response.setPeopleList(protoPeople);
            callback(null, response);
          })
          .catch((error) => {
            const grpcError = {
              code: grpc.status.INTERNAL,
              message: `failed to fetch list of people: ${error.message}`
            };
            callback(grpcError, null);
          });
  }
}

function run() {
  const pluginServer = new PluginServer();
  pluginServer.addService(StarwarsServiceService, new StarwarsService());
  pluginServer.serve().catch((error) => {
    console.error('Failed to start plugin server:', error);
    process.exit(1);
  });
}

run();
```
