Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/moveaxlab/nestjs-grpc-client

A dataloader implementation to talk with gRPC servers, with support for request merging and cross-request caching.
https://github.com/moveaxlab/nestjs-grpc-client

dataloader graphql grpc nestjs

Last synced: about 2 months ago
JSON representation

A dataloader implementation to talk with gRPC servers, with support for request merging and cross-request caching.

Awesome Lists containing this project

README

        

# NestJS gRPC Client
![NPM](https://img.shields.io/npm/l/%40moveaxlab%2Fgraphql-client)
[![npm](https://img.shields.io/npm/v/@moveaxlab/nestjs-grpc-client)](https://www.npmjs.com/package/@moveaxlab/nestjs-grpc-client)
![Static Badge](https://img.shields.io/badge/node_version-_%3E%3D20-green)

This library defines gRPC dataloaders you can inject dynamically in GraphQL resolvers in order to optimize calls via batching and caching.

More about the dataloader pattern at https://github.com/graphql/dataloader

## Installation

```bash
yarn add @moveaxlab/nestjs-grpc-client
```

## Usage

### Dataloaders

gRPC dataloaders are based on gRPC clients defined as follows:
```typescript
// package.client.ts
import { GrpcClientForService } from '@moveax/nestjs-grpc-client';
import { grpcPkg } from 'somewhere'; // import also your autogenerated gRPC package

export type PackageClient = GrpcClientForService<
grpcPkg.PackageServiceDefinition
>;
// OR
export type PackageClientWithStreamingMethods = GrpcClientForService<
grpcPkg.PackageServiceDefinition,
'myClientStreamingMethod' | 'myBidirectionalStreamingMethod'
>;
```

Implement your gRPC dataloader:
```typescript
// package.dataloader.ts
import { Metadata } from '@grpc/grpc-js';
import { GrpcDataLoaderProvider, DataLoaderForClient, Request, createDataLoaderDecorator } from '@moveax/nestjs-grpc-client';
import { Injectable, Logger } from '@nestjs/common';
import { PackageClient } from 'package.client';
import { grpcPkg } from 'somewhere'; // import also your autogenerated gRPC package

@Injectable()
export class PackageDataLoaderProvider extends GrpcDataLoaderProvider {
client: PackageClient;
logger = new Logger(PackageDataLoaderProvider.name);

createMetadata(_: Request): Metadata {
const metadata = new Metadata();
// you can use the Express' request token to authenticate also the gRPC call
metadata.set('authorization', req.get('authorization'));
return metadata;
}

get cacheConfig() {
return {
someMethod: {
cacheKeyFn: (request: grpcPkg.ISomeMethodRequest) => {
return `pkgService.someMethod:${input.param1}-${input.param2}`;
},
ttl: 60,
},
};
}
}

export const PackageService = createDataLoaderDecorator(PackageDataLoaderProvider.prototype);

export type PackageDataloader = DataLoaderForClient;
```

1. The `cacheConfig` getter returns an object containing cache configuration for the various methods.
If a method needs to be cached, the `cacheConfig` must contain an entry for that method that takes in input
the method request and returns a string, which will be used as the cache key. A TTL can be specified,
that will be used if global caching is enabled (see below).
2. The `PackageService` is an object containing a parameter decorator for each method of the gRPC client.
You can use the decorator in your resolvers to obtain an instance of the dataloader (see below).
3. The `PackageDataloader` is a type containing the types of each method dataloader.
You can use the type in your resolvers to add type safety to your loaders.

Add the `GrpcDataLoaderInterceptor` to your app in order to load dataloaders relevant for the request at runtime:
```typescript
import { Module } from '@nestjs/common';
import { APP_INTERCEPTOR } from '@nestjs/core';
import { GrpcDataLoaderInterceptor } from '@moveax/nestjs-grpc-client';

@Module({
provides: [
{
provide: APP_INTERCEPTOR,
useClass: GrpcDataLoaderInterceptor,
}
],
/* ... */
})
export class AppModule {}
```

Now you can use the dataloader in your resolvers:
```typescript
// imports omitted for lack of will

@Resolver()
class Whatever {
@Query()
async getWhatever(
@PackageService.someMethod() loader: PackageDataloader['someMethod']
) {
const response = await loader.load(/* gRPC request */);
// do whatever you want with the response now
}
}
```

### Batching requests

To batch several requests to a given service when doing API composition,
specify the `mergeConfig` inside your dataloader provider.

The `mergeConfig` provides two properties for each endpoint:
- `mergeInput`, that takes an array of requests and combines them to a single request
- `splitOutput`, that takes the original array of requests and a single response,
and splits it into an array of responses

### Global caching

To use global caching, pass `true` to the loader decorator:
```typescript
// imports omitted for lack of will

@Resolver()
class Whatever {
@Query()
async getWhatever(
@PackageService.someMethod(true) loader: PackageDataloader['someMethod']
) {
const response = await loader.load(/* gRPC request */);
// do whatever you want with the response now
}
}
```

The `ttl` must be set to something greater than 0 inside the cacheConfig
for the dataloader, for global caching to work. _The TTL is expressed in seconds._

All requests to that specific dataloader will be cached for `ttl` seconds,
in a cache shared between all calls. __Use global caching with care:__ the global cache will skip all authorization logic
that may live inside your backend services.