Skip to main content

Use a service from the client side

This article covers how to use a service in the client side of your App.

Overview

Root processes each of your protobuf services to generate client-side code and puts it in your networking/gen/client/src folder. The Root-generated code is fully implemented and ready to use.

What Root generates:

  1. A class clients use to access service.
  2. Fully implemented request-response methods.
  3. An event for each broadcast method.
  4. Types that describe the available events.
  5. Registered instance of the generated class.

How you use it:

  1. Import the instance.
  2. Call request response methods.
  3. Subscribe to events of interest.

Root-generated client-side code

Let's look at the client-side code Root generates. We'll again use the create operation from the SuggestionService as our example:

SuggestionService and its create operations
service SuggestionService {
rpc Create(SuggestionCreateRequest) returns (SuggestionCreateResponse);

rpc BroadcastCreated(SuggestionCreatedEvent) returns (rootsdk.Void);

// ...
}

Here's the complete client-side code generated from the two create methods. The generated code is . We'll go through this step-by-step below.

Root-generated client-side TypeScript
// Defines the available events
export type SuggestionServiceClientEvents = {
'broadcastCreated': (event: SuggestionCreatedEvent) => void
}

// Convenience enum to avoid using hardcoded strings to subscribe to events
export enum SuggestionServiceClientEvent {
Created = 'broadcastCreated'
}

// Class is an event emitter and contains the request-response methods
export class SuggestionServiceClient extends (EventEmitter as new() => TypedEventEmitter<SuggestionServiceClientEvents>) implements RootClientService {
create(request: SuggestionCreateRequest):Promise<SuggestionCreateResponse> {
// Method is fully implemented (not shown) ...
}
}

// Root creates and registers an instance
export const suggestionServiceClient = new SuggestionServiceClient();
(<IRootClient><unknown>rootClient).addClient(suggestionServiceClient);

Class generation

First, Root processes the service itself.

Protobuf source for the SuggestionService
service SuggestionService {
// ...
}

Root generates a class for your service. The name of the class is your service name with Client added.

Generated client-side class for the SuggestionService
export class SuggestionServiceClient extends (EventEmitter as new() => TypedEventEmitter<SuggestionServiceClientEvents>) implements RootClientService
{
}

Request-response method generation

Next, Root processes your request-response methods.

Protobuf source for the Create operation
service SuggestionService {
rpc Create(SuggestionCreateRequest) returns (SuggestionCreateResponse);
// ...

For request-response methods, Root generates an implemented method (create in this case) that's ready for the client to call.

Generated client-side code for the Create method
export class SuggestionServiceClient extends (EventEmitter as new() => TypedEventEmitter<SuggestionServiceClientEvents>) implements RootClientService {
create(request: SuggestionCreateRequest):Promise<SuggestionCreateResponse> {
// Method is fully implemented (not shown) ...
}
}

Broadcast method generation

Root processes your broadcast methods into client-side events.

Protobuf source for BroadcastCreated method
service SuggestionService {
rpc BroadcastCreated(SuggestionCreatedEvent) returns (rootsdk.Void);
// ...
}

There's one event generated for each broadcast method. The SuggestionServiceClient class is an EventEmitter that lets clients register for any of the events they're interested in.

Generated client-side events
export class SuggestionServiceClient extends (EventEmitter as new() => TypedEventEmitter<SuggestionServiceClientEvents>) implements RootClientService
{
// ...
}

In addition, there are two types that help clients subscribe to the events. First, there the actual type used with the EventEmitter (note the 's' on the end of the type name SuggestionServiceClientEvents).

Client-side event type
export type SuggestionServiceClientEvents = {
'broadcastCreated': (event: SuggestionCreatedEvent) => void
}

Then there's a convenience enum that has a value for each event (note that there's not* an 's' on the end of the type name SuggestionServiceClientEvent). You'll use the enum values to register for events instead of hardcoding the associated string.

Enum to simplify event subscription
export enum SuggestionServiceClientEvent {
Created = 'broadcastCreated'
}

Instance registration

The generated code also creates an instance of the client type and registers the instance with Root. These steps make the client object fully ready for use in your client code.

Instantiate and register an instance of the client service
export const suggestionServiceClient = new SuggestionServiceClient();

(<IRootClient><unknown>rootClient).addClient(suggestionServiceClient);

Use the generated client-side code in your App

Now we'll walk through the code you write on the client to use your service. We'll continue using the create operation from the SuggestionService as our example.

Imports

You'll need to import several types. The import locations are determined by the options you specified in your root-protoc.json file. Typically, this would be something like @suggestionbox/gen-client for client-specific types and @suggestionbox/gen-shared for data-transfer objects (DTOs).

Import the DTOs, client instance, and enum
import {
// DTOs
Suggestion,
SuggestionCreateRequest,
SuggestionCreateResponse,
SuggestionCreatedEvent
} from "@suggestionbox/gen-shared";

import {
// Service client instance
suggestionServiceClient,

// Convenience enum to simplify event subscription
SuggestionServiceClientEvent,
} from "@suggestionbox/gen-client";

Call request-response methods

You call request-response method on the client instance you imported. All request-response methods are async on the client.

Here's how you might call the create method.

Call a request-response method
const text: string = // retrieve text from the UI

const request: SuggestionCreateRequest = { text };
const response: SuggestionCreateResponse = await suggestionServiceClient.create(request);

// The suggestion field is nullable since it was generated from a protobuf message type
const createdSuggestion: Suggestion = response.suggestion!;

// Update your UI with the new suggestion this client created ...

Subscribe to events

The suggestionServiceClient instance is an event emitter. It has the standard event methods like on and off to let you subscribe and unsubscribe to events.

Here's how you might use the Created event in your client to be notified when other clients create new suggestions. Notice the use of the enum value SuggestionServiceClientEvent.Created to specify they event you're interested in.

Subscribe to an event
suggestionServiceClient.on(SuggestionServiceClientEvent.Created, onCreated);
Unsubscribe from an event
suggestionServiceClient.off(SuggestionServiceClientEvent.Created, onCreated);
Handle an event
// Handle incoming event (when another client creates a new suggestion)
const onCreated = (event: SuggestionCreatedEvent) => {

// The suggestion field is nullable since it was generated from a protobuf message type
const createdSuggestion: Suggestion = event.suggestion!;

// Update your UI with the new suggestion
};

Conclusion

Once Root generates your client-side service code, using it is straightforward. You import the instance, call methods like create, and subscribe to events using standard patterns. The generated code takes care of the network addressing, data transfer, and authentication so you can focus on building your App’s features.