Skip to content

Commit a21634d

Browse files
luizgribeirobeeme1mrlukas-reining
authored
docs: Nest SDK (#750)
## This PR - Adds docs to the NestJS SDK. ### Notes I think we're probably reaching a stable point for the NestJS SDK. It would be great to have some feedback on the docs before it. ### Follow-up Tasks - Blog post & announcement regarding the new SDK (maybe?) --------- Signed-off-by: Luiz Ribeiro <[email protected]> Signed-off-by: Luiz Guilherme Ribeiro <[email protected]> Signed-off-by: Lukas Reining <[email protected]> Co-authored-by: Michael Beemer <[email protected]> Co-authored-by: Lukas Reining <[email protected]>
1 parent 2f59a9f commit a21634d

File tree

4 files changed

+94
-41
lines changed

4 files changed

+94
-41
lines changed

packages/nest/README.md

+66-39
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,19 @@
77
</picture>
88
</p>
99

10-
<h2 align="center">OpenFeature Nest.js SDK</h2>
10+
<h2 align="center">OpenFeature NestJS SDK</h2>
1111

1212
<!-- x-hide-in-docs-end -->
1313
<!-- The 'github-badges' class is used in the docs -->
1414
<p align="center" class="github-badges">
1515
<a href="https://github.com/open-feature/spec/releases/tag/v0.7.0">
1616
<img alt="Specification" src="https://img.shields.io/static/v1?label=specification&message=v0.7.0&color=yellow&style=for-the-badge" />
1717
</a>
18-
<br/>
18+
<!-- x-release-please-start-version -->
19+
<a href="https://github.com/open-feature/js-sdk/releases/tag/nestjs-sdk-v0.1.0">
20+
<img alt="Release" src="https://img.shields.io/static/v1?label=release&message=v0.1.0&color=blue&style=for-the-badge" />
21+
</a>
22+
<!-- x-release-please-end -->
1923
<a href="https://codecov.io/gh/open-feature/js-sdk">
2024
<img alt="codecov" src="https://codecov.io/gh/open-feature/js-sdk/branch/main/graph/badge.svg?token=3DC5XOEHMY" />
2125
</a>
@@ -25,11 +29,49 @@
2529
[OpenFeature](https://openfeature.dev) is an open specification that provides a vendor-agnostic, community-driven API
2630
for feature flagging that works with your favorite feature flag management tool.
2731

28-
🧪 This SDK is experimental.
32+
<!-- x-hide-in-docs-end -->
33+
34+
## Overview
35+
36+
The OpenFeature NestJS SDK is a package that provides a NestJS wrapper for the [OpenFeature Server SDK](https://openfeature.dev/docs/reference/technologies/server/javascript/).
37+
38+
Capabilities include:
39+
40+
- Provide a NestJS global module to simplify OpenFeature configuration and usage within NestJS;
41+
- Injecting feature flags directly into controller route handlers by using decorators;
42+
- Injecting transaction evaluation context for flag evaluations directly from [execution context](https://docs.nestjs.com/fundamentals/execution-context) (HTTP header values, client IPs, etc.);
43+
- Injecting OpenFeature clients into NestJS services and controllers by using decorators;
44+
- Setting up logging, event handling, hooks and providers directly when registering the module.
45+
46+
## 🚀 Quick start
47+
48+
### Requirements
49+
50+
- Node.js version 16+
51+
- NestJS version 8+
52+
53+
### Install
54+
55+
#### npm
56+
57+
```sh
58+
npm install --save @openfeature/nestjs-sdk
59+
```
60+
61+
#### Required peer dependencies
2962

30-
#### Here's a basic example of how to use the OpenFeature NestJS API with `InMemoryProvider`.
63+
The following list contains the peer dependencies of `@openfeature/nestjs-sdk` with it's expected and compatible versions:
3164

32-
#### Registering the Nest.js SDK module in the App Module:
65+
* `@openfeature/server-sdk`: >=1.7.5
66+
* `@nestjs/common`: ^8.0.0 || ^9.0.0 || ^10.0.0
67+
* `@nestjs/core`: ^8.0.0 || ^9.0.0 || ^10.0.0
68+
* `rxjs`: ^6.0.0 || ^7.0.0 || ^8.0.0
69+
70+
The minimum required version of `@openfeature/server-sdk` currently is `1.7.5`.
71+
72+
### Usage
73+
74+
The example below shows how to use the `OpenFeatureModule` with OpenFeature's `InMemoryProvider`.
3375

3476
```ts
3577
import { Module } from '@nestjs/common';
@@ -44,24 +86,19 @@ import { InMemoryProvider } from '@openfeature/web-sdk';
4486
testBooleanFlag: {
4587
defaultVariant: 'default',
4688
variants: { default: true },
47-
disabled: false
89+
disabled: false,
4890
},
49-
companyName: {
50-
defaultVariant: 'default',
51-
variants: { default: "BigCorp" },
52-
disabled: false
53-
}
5491
}),
5592
providers: {
56-
differentProvider: new InMemoryProvider()
57-
}
58-
})
59-
]
93+
differentProvider: new InMemoryProvider(),
94+
},
95+
}),
96+
],
6097
})
6198
export class AppModule {}
6299
```
63100

64-
#### Injecting a feature flag with header value in evaluation context into an endpoint handler method
101+
With the `OpenFeatureModule` configured, it's possible to inject flag evaluation details into route handlers like in the following code snippet.
65102

66103
```ts
67104
import { Controller, ExecutionContext, Get } from '@nestjs/common';
@@ -70,40 +107,26 @@ import { BooleanFeatureFlag } from '@openfeature/nestjs-sdk';
70107
import { EvaluationDetails } from '@openfeature/server-sdk';
71108
import { Request } from 'express';
72109

73-
function getContext(executionContext: ExecutionContext) {
74-
const request = executionContext.switchToHttp().getRequest<Request>();
75-
const userId = request.header('x-user-id');
76-
77-
if (!userId) {
78-
return undefined;
79-
}
80-
81-
return {
82-
targetingKey: userId,
83-
};
84-
}
85-
86110
@Controller()
87111
export class OpenFeatureController {
88112
@Get('/welcome')
89113
public async welcome(
90114
@BooleanFeatureFlag({
91115
flagKey: 'testBooleanFlag',
92116
defaultValue: false,
93-
contextFactory: getContext,
94117
})
95-
feature: Observable<EvaluationDetails<boolean>>,
118+
feature: Observable<EvaluationDetails<boolean>>,
96119
) {
97120
return feature.pipe(
98121
map((details) =>
99-
details.value ? 'Welcome to this OpenFeature-enabled Nest.js app!' : 'Welcome to this Nest.js app!',
122+
details.value ? 'Welcome to this OpenFeature-enabled NestJS app!' : 'Welcome to this NestJS app!',
100123
),
101124
);
102125
}
103126
}
104127
```
105128

106-
#### Injecting the default and a named client into a service:
129+
It is also possible to inject the default or named OpenFeature clients into a service via Nest dependency injection system.
107130

108131
```ts
109132
import { Injectable } from '@nestjs/common';
@@ -114,15 +137,19 @@ import { FeatureClient } from '@openfeature/nestjs-sdk';
114137
export class OpenFeatureTestService {
115138
constructor(
116139
@FeatureClient() private defaultClient: Client,
117-
@FeatureClient({ name: 'differentServer' }) private namedClient: Client,
118-
) {
119-
}
140+
@FeatureClient({ name: 'differentProvider' }) private namedClient: Client,
141+
) {}
120142

121-
public async getMessage() {
122-
const companyName = await this.defaultClient.getStringValue('companyName', 'Unknown Company');
123-
return `Hey User from ${companyName}`;
143+
public async getBoolean() {
144+
return await this.defaultClient.getBooleanValue('testBooleanFlag', false);
124145
}
125146
}
126147
```
127148

149+
## Module aditional information
150+
151+
### Flag evaluation context injection
128152

153+
Whenever a flag evaluation occurs, context can be provided with information like user e-mail, role, targeting key, etc in order to trigger specific evaluation rules or logic. The `OpenFeatureModule` provides a way to configure context for each request using the `contextFactory` option.
154+
The `contextFactory` is ran in a NestJS interceptor scope to configure the evaluation context and than it is used in every flag evaluation related to this request.
155+
By default, the interceptor is configured globally, but it can be changed by setting the `useGlobalInterceptor` to `false`. In this case, it is still possible to configure a `contextFactory` that can be injected into route, module or controller bound interceptors.

packages/nest/src/evaluation-context-interceptor.ts

+24
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,31 @@ import { CallHandler, ExecutionContext, Inject, Injectable, NestInterceptor } fr
22
import { ContextFactory, ContextFactoryToken } from './context-factory';
33
import { Observable } from 'rxjs';
44
import { OpenFeature } from '@openfeature/server-sdk';
5+
import { OpenFeatureModule } from './open-feature.module';
56

7+
/**
8+
* NestJS interceptor used in {@link OpenFeatureModule}
9+
* to configure flag evaluation context.
10+
*
11+
* This interceptor is configured globally by default.
12+
* If `useGlobalInterceptor` is set to `false` in {@link OpenFeatureModule} it needs to be configured for the specific controllers or routes.
13+
*
14+
* If just the interceptor class is passed to the `UseInterceptors` like below, the `contextFactory` provided in the {@link OpenFeatureModule} will be injected and used in order to create the context.
15+
* ```ts
16+
* //route interceptor
17+
* @UseInterceptors(EvaluationContextInterceptor)
18+
* @Get('/user-info')
19+
* getUserInfo(){}
20+
* ```
21+
*
22+
* A different `contextFactory` can also be provided, but the interceptor instance has to be instantiated like in the following example.
23+
* ```ts
24+
* //route interceptor
25+
* @UseInterceptors(new EvaluationContextInterceptor(<context factory>))
26+
* @Get('/user-info')
27+
* getUserInfo(){}
28+
* ```
29+
*/
630
@Injectable()
731
export class EvaluationContextInterceptor implements NestInterceptor {
832
constructor(@Inject(ContextFactoryToken) private contextFactory?: ContextFactory) {}

packages/nest/src/evaluation-context-propagator.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
import { TransactionContextPropagator } from '@openfeature/server-sdk';
1+
import { TransactionContextPropagator, EvaluationContext } from '@openfeature/server-sdk';
22
import { AsyncLocalStorage } from 'async_hooks';
3-
import { EvaluationContext } from '@openfeature/server-sdk';
43

54
export class AsyncLocalStorageTransactionContext implements TransactionContextPropagator {
65
private asyncLocalStorage = new AsyncLocalStorage<EvaluationContext>();

packages/nest/src/open-feature.module.ts

+3
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ import { AsyncLocalStorageTransactionContext } from './evaluation-context-propag
2323
import { EvaluationContextInterceptor } from './evaluation-context-interceptor';
2424
import { ShutdownService } from './shutdown.service';
2525

26+
/**
27+
* OpenFeatureModule is a NestJS wrapper for OpenFeature Server-SDK.
28+
*/
2629
@Module({})
2730
export class OpenFeatureModule {
2831
static forRoot({ useGlobalInterceptor = true, ...options }: OpenFeatureModuleOptions): DynamicModule {

0 commit comments

Comments
 (0)