Services

Overview

Services provide a flexible way to:

  1. run server code that can be isomorphically executed from the client or server while
  2. executing that code either on a web server or a background worker-type server.

A lot of boilerplate code is eliminated since server-side routes and controllers and client-side service code does not need to be written since Tyranid handles the marshaling and unmarshaling of data automatically across the HTTP call.

Furthermore, Tyranid services are strongly type-checked at both the client- and server-level using both run-time type checking and compile-time type-checking (via generated TypeScript interfaces).

Client calls to server methods will also pass through the standard fromClient() and toClient() mechanisms defined on types, fields, and documents.

Tyranid will also perform the following processing on service return values:

Rethrowable exceptions thrown from inside services will be caught and rethrown in the client to ensure that error handling is also done isomorphically.

Because exceptions are caught and rethrown in the client, this means you should generally not return error codes or error messages from your services -- throw AppError, SecureError, or UserError exceptions instead.

API

To implement a service:

  1. Define your service metadata in the collection definition under the service option.

    const MyCollection = new Tyr.Collection({
      ...,
      service: {
        my service methods
      },
      ...
    }) as Tyr.MyCollectionCollection;
  2. Implement the MyCollectionService interface that tyranid-tdgen generates from your service metadata and register it with the collection by assigning to service:
    MyCollection.service = {
      async myServiceMethod1(myServiceMethod1Parameters ...) {
        ...
      },
      async myServiceMethod2(myServiceMethod2Parameters ...) {
        ...
      },
      ...
    };

    Note that MyCollection.service is typed by tyranid-tdgen to implement MyCollectionService.

this Context

Inside service methods, this is available with the following properties:

OptionNotes
{
auth:The authorization object (usually a user) if the request was invoked from the client, otherwise undefined.
req:The express request object if the service was invoked from the client, otherwise undefined.
res:The express response object if the service was invoked from the client, otherwise undefined.
source:One of 'client' or 'server'.
user:The user making the request if the request was invoked from the client, otherwise undefined.
}

Example

Server /user.model.ts

const User = new Tyr.Collection({
  ...,
  service: {
    activationCodesCsv: {
      route: '/api/user/activationCodesCsv',
      help: ‘Lorem ipsum dolor sit amet, ...’,
      params: {
        orgIds: {
          help: ‘Amet dolor sit ipsum’,
          is: 'array',
          of: { link: 'organization' }
        },
        groupIds: { is: 'array', of: { link: 'group' } },
      },
      return: {
        is: 'object',
        help: ‘Lorem ipsum dolor sit amet, ...’ },
        fields: {
          userId: { link: ‘user’ },
          activationCode: { is: ‘url’ },
        }
      }
    }
  },
  ...
}) as Tyr.UserCollection;

// could also be defined in a user.service.ts file or someplace else
User.service = {
  async activationCodesCsv(orgIds: ObjectId[], groupIds: ObjectId[]) {
    …
  }
};

Note that the

route: '/api/user/activationCodesCsv/',

line is redundant because the default route format is:

/api/collection name/service method name

Generated by tyranid-tdgen

interface UserService {
  /**
   * Lorem ipsum dolor sit amet, ...
   * @param orgIds Amet dolor sit ipsum
   * @return Lorem ipsum dolor sit amet, ...
   */
  activationCodesCsv(orgIds: ObjectId[], groupIds: ObjectId[]):
    Promise<{ userId: ObjectId, activationCode: string }>;
}
In addition, the following line will be injected into the type definition for a collection, ensuring that service methods are implemented correctly.
...
service: UserService;
...

Example Usage

The example is the same for both client and server code.

const activationCodes = await User.activationCodesCsv(
  userIds: myUserIds, groupIds: myGroupIds);

Roadmap