Basic Client#

This guide provides a step-by-step walkthrough for setting up a basic client to interact with the Ridepooling API. Follow the instructions to authenticate and connect to the API in order to make requests. General gRPC and Protobuf knowledge is recommended for working with this example.

To familiarize yourself with gRPC or Protobuf, check out this introduction to gRPC and this introduction to Protobuf.

This example refers to the TripService and is setting up an example client for this specific service. It can be applied to any other service by modifying the code and using a different generated code. The sections which need to be adjusted are marked in the code examples.

Project setup#

We will use buf to generate a client for the Ridepooling API. The commands below will set up a new project and install the required dependencies.

  1. Download the Protobuf definitions ridepooling_api.zip and create the project using the commands:

mkdir ridepooling-client && cd ridepooling-client
unzip your-downloads/ridepooling_api.zip -d .
  1. Create buf configuration files with the following contents:

  • buf.gen.yaml

version: v2
managed:
  enabled: true
  override:
    - file_option: go_package_prefix
      # replace with your module name
      value: moia.io/ridepoolingapi-client
plugins:
  - local: protoc-gen-go
    out: ./
    opt: paths=source_relative
    include_imports: true
  - local: protoc-gen-go-grpc
    out: ./
    opt: paths=source_relative
    include_imports: true
inputs:
  - directory: protos
  • buf.yaml

version: v2
modules:
  - path: ./protos/
  • buf.gen.yaml

version: v2
plugins:
  - local: ./node_modules/ts-proto/protoc-gen-ts_proto
    out: ./
    strategy: all
    opt:
      - outputServices=generic-definitions
      - outputServices=nice-grpc
      - esModuleInterop=true
      - useExactTypes=false
  1. Run the following commands to install some project dependencies and generate the client code:

go mod init moia.io/ridepoolingapi-client
go mod tidy

go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
buf generate
npm init -y
tsc --init

npm install @bufbuild/buf nice-grpc ts-proto
npx buf generate
  1. Create the client file and run it.

touch client.go
CLIENT_ID=<your-client-id> CLIENT_SECRET=<your-client-secret> go run client.go

We will use some dependencies during this tutorial, depending on your setup you might need to repeatedly run go mod tidy or go get in order to install them.

We can use ts-node to quickly run our TypeScript code.

touch client.ts
CLIENT_ID=<your-client-id> CLIENT_SECRET=<your-client-secret> npx ts-node client.ts

File Structure#

package main

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

	"github.com/google/uuid"
	grpc_retry "github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/retry"
	"golang.org/x/oauth2"
	"golang.org/x/oauth2/clientcredentials"
	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/credentials"
	"google.golang.org/grpc/credentials/oauth"
	"google.golang.org/grpc/metadata"
	"google.golang.org/protobuf/encoding/protojson"
	"google.golang.org/protobuf/types/known/timestamppb"
	tripv1beta2 "moia.io/ridepoolingapi-client/moia/ridepooling/trip/v1beta2"
	types_v1 "moia.io/ridepoolingapi-client/moia/type/v1"
)

func main() {
	// The code of the next steps goes here
}
import * as grpc from "nice-grpc";
import {
  TripServiceDefinition,
  Wheelchair,
  ChildSeat,
} from "./moia/ridepooling/trip/v1beta2/trip";
import { connect as http2_connect } from "http2";
import { v4 as uuidv4 } from "uuid";
import { retryMiddleware } from "nice-grpc-client-middleware-retry";

async function main() {
  // The code of the next steps goes here
}

main();

Authenticate#

To authenticate, we need to obtain an access token from the API gateway. For more information on authentication see the Authentication section.

To abtain a token, we will use the golang.org/x/oauth2 library. You can also use an OAuth2 client, as long as you authenticate with the client_credentials grant type and use the authorization header with the Basic scheme.

clientId := os.Getenv("CLIENT_ID")
clientSecret := os.Getenv("CLIENT_SECRET")
const tokenUrl = "https://ridepooling-api.int.eu-central-1.moia-group.io/auth/oauth/token"

oauthConfig := clientcredentials.Config{
	ClientID:     clientId,
	ClientSecret: clientSecret,
	TokenURL:     tokenUrl,
	AuthStyle:    oauth2.AuthStyleInHeader,
}

httpCtx := context.Background()
httpClient := &http.Client{Timeout: 30 * time.Second}
httpCtx = context.WithValue(httpCtx, oauth2.HTTPClient, httpClient)

// Used in the next steps
accessToken, err := oauthConfig.Token(httpCtx)
if err != nil {
	log.Fatalf("Could not fetch token: %v", err)
}

To obtain a token we will send an HTTP request to the token URL using the fetch API. We can also use an OAuth2 client, as long as you authenticate with the grant type client_credentials and use the Authorization header with the Basic scheme.

const clientId = process.env.CLIENT_ID;
const clientSecret = process.env.CLIENT_SECRET;
const tokenUrl =
  "https://ridepooling-api.int.eu-central-1.moia-group.io/auth/oauth/token";

const authHeader = Buffer.from(`${clientId}:${clientSecret}`).toString(
  "base64"
);

// The Auth Endpoint is only accessible via HTTP/2
const http2AuthClient = http2_connect(tokenUrl);
const tokenRequest = http2AuthClient.request({
  ":method": "POST",
  ":path": "/auth/oauth/token",
  authorization: `Basic ${authHeader}`,
  "content-type": "application/x-www-form-urlencoded",
});
tokenRequest.setEncoding("utf8");
const requestBody = "grant_type=client_credentials";

tokenRequest.write(requestBody);
tokenRequest.end();

let data = "";
for await (const chunk of tokenRequest) {
  data += chunk;
}
const tokenResponse = JSON.parse(data) as Record<string, unknown>;
if (tokenResponse.error) {
  console.error(tokenResponse.error_description);
  throw Error("Fetching Authentication Token failed");
}
http2AuthClient.close();

// Used in the next steps
const accessToken = tokenResponse.access_token;

Connect to the API#

We setup SSL credentials for transport and supply our token with every request using grpc.WithPerRPCCredentials.

const apiUrl = "ridepooling-api.int.eu-central-1.moia-group.io:443"

perRpcCredentials := oauth.TokenSource{
	TokenSource: oauth2.StaticTokenSource(accessToken),
}
conn, err := grpc.NewClient(
	apiUrl,
	grpc.WithTransportCredentials(
		credentials.NewClientTLSFromCert(nil, ""),
	),
	grpc.WithPerRPCCredentials(perRpcCredentials),
	grpc.WithUnaryInterceptor(grpc_retry.UnaryClientInterceptor(retryOptions...)),
)
if err != nil {
	log.Fatalf("Could not connect to %s: %v", apiUrl, err)
}
defer conn.Close()

// Create a different ServiceClient if you want to create a client for a different gRPC Service
tripClient := tripv1beta2.NewTripServiceClient(conn)

ctx := context.Background()

// Used for printing responses
jsonMarshalOptions := protojson.MarshalOptions{
	Multiline:       true,
	EmitUnpopulated: true,
}

We use nice-grpc to set up a gRPC channel and client. We use the grpc.Metadata class to add the Authorization header to all of our gRPC requests.

const apiUrl = "ridepooling-api.int.eu-central-1.moia-group.io:443";

// client Factory with middleware to insert authorization token to every request
const clientFactory = grpc
  .createClientFactory()
  .use((call, options) =>
    call.next(call.request, {
      ...options,
      metadata: grpc
        .Metadata(options.metadata)
        .set("Authorization", `Bearer ${accessToken}`),
    })
  )
  .use(retryMiddleware);
const channel = grpc.createChannel(
  apiUrl,
  grpc.ChannelCredentials.createSsl()
);

// Insert a different generated ServiceDefinition if you want to create a client for a different gRPC Service
const client = clientFactory.create(TripServiceDefinition, channel);

Continue with the API Usage example where we will call some of the Ridepooling API methods.