Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore(examples): Show dynamic feature flags with LaunchDarkly #1100

Merged
merged 11 commits into from
Jul 15, 2024
7 changes: 7 additions & 0 deletions examples/nodejs-express-launchdarkly/.env.local.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# NODE_ENV is not set by the environment
ARCJET_ENV=development
# Add your Arcjet key from https://app.arcjet.com
ARCJET_KEY=
# Add your LaunchDarkly SDK key. Click on the project name in this list
# to be taken to the SDK keys: https://app.launchdarkly.com/settings/projects
LAUNCHDARKLY_SDK_KEY=
104 changes: 104 additions & 0 deletions examples/nodejs-express-launchdarkly/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
<a href="https://arcjet.com" target="_arcjet-home">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://arcjet.com/logo/arcjet-dark-lockup-voyage-horizontal.svg">
<img src="https://arcjet.com/logo/arcjet-light-lockup-voyage-horizontal.svg" alt="Arcjet Logo" height="128" width="auto">
</picture>
</a>

# Arcjet Dynamic Configuration via LaunchDarkly Feature Flags

This example shows how to dynamically configure Arcjet via
[LaunchDarkly](https://launchdarkly.com) feature flags. It is implemented with a Node.js
[Express](https://expressjs.com/) server, but the theory can apply to any environment.

## How to use

1. From the root of the project, install the SDK dependencies.

```bash
npm ci
```

2. Enter this directory and install the example's dependencies.

```bash
cd examples/nodejs-express-launchdarkly
npm ci
```

3. Rename `.env.local.example` to `.env.local` and add your [Arcjet key](https://app.arcjet.com)
and LaunchDarkly SDK key ([click on the project name in this list to be taken to the SDK
keys](https://app.launchdarkly.com/settings/projects)).

4. Create four flags in LaunchDarkly with the following settings:
- Shield Mode:
- Name: `shieldMode`
- Key: `shieldMode`
- Configuration: **Custom**
- Is this flag temporary? No
- Flag Type: String
- Variations
1. Name: `LIVE`, value: `LIVE`
2. Name: `DRY_RUN`, value: `DRY_RUN`
- Default Variations
* Serve when targeting is ON: `LIVE`
* Serve when targeting is OFF: `DRY_RUN`
- Sliding Window Mode:
- Name: `slidingWindowMode`
- Key: `slidingWindowMode`
- Configuration: **Custom**
- Is this flag temporary? No
- Flag Type: String
- Variations
1. Name: `LIVE`, value: `LIVE`
2. Name: `DRY_RUN`, value: `DRY_RUN`
- Default Variations
* Serve when targeting is ON: `LIVE`
* Serve when targeting is OFF: `DRY_RUN`
- Sliding Window Max:
- Name: `slidingWindowMax`
- Key: `slidingWindowMax`
- Configuration: **Custom**
- Is this flag temporary? No
- Flag Type: Number
- Variations
1. Name: `Regular`, value: `100`
2. Name: `Clamped Down`, value: `2`
- Default Variations
* Serve when targeting is ON: `Regular`
* Serve when targeting is OFF: `Clamped Down`
- Sliding Window Interval:
- Name: `slidingWindowInterval`
- Key: `slidingWindowInterval`
- Configuration: **Custom**
- Is this flag temporary? No
- Flag Type: Number
- Variations
1. Name: `Regular`, value: `60`
2. Name: `Clamped Down`, value: `10`
- Default Variations
* Serve when targeting is ON: `Regular`
* Serve when targeting is OFF: `Clamped Down`

5. Start the server.

```bash
npm start
```

This assumes you're using Node.js 20 or later because the `start` script
loads a local environment file with `--env-file`. If you're using an older
version of Node.js, you can use a package like
[dotenv](https://www.npmjs.com/package/dotenv) to load the environment file.

6. Visit `http://localhost:3000/`.

7. Refresh the page about 5-10 times. The rate-limit should not be hit.

8. Go to the "Contexts" section in the LaunchDarkly dashboard and click on "guest".

9. Change the `slidingWindowMax` and `slidingWindowInterval` to `Clamped Down`,
click "Review and save", then "Save changes" in the modeal that appears.

10. Refresh your web application and note the new configuration is announced in
the terminal. Refresh another few times to see the rate-limit take effect.
27 changes: 27 additions & 0 deletions examples/nodejs-express-launchdarkly/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import arcjet from "./lib/arcjet.js";
import express from "express";

const app = express();
app.get("/", async (req, res) => {
// Get an instance of Arcjet from our custom module
const aj = await arcjet();

// Get a decision from Arcjet for the incoming request
const decision = await aj.protect(req);

// If the decision is denied, return an appropriate status code
if (decision.isDenied()) {
if (decision.reason.isRateLimit()) {
return res.status(429).send("Too many requests");
} else {
return res.status(403).send("Forbidden");
}
}

// If the decision is allowed, return a successful response
res.send("Hello World!");
});

app.listen(3000, () => {
console.log("Server started on port 3000");
});
28 changes: 28 additions & 0 deletions examples/nodejs-express-launchdarkly/lib/arcjet.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import _arcjet, { shield, slidingWindow } from "@arcjet/node";
import { getArcjetConfig } from "./launchdarkly.js";

// Initialize Arcjet with your site key and rules
const aj = _arcjet({
bendechrai marked this conversation as resolved.
Show resolved Hide resolved
// Get your site key from https://app.arcjet.com
// and set it as an environment variable rather than hard coding.
// See: https://www.npmjs.com/package/dotenv
key: process.env.ARCJET_KEY,
rules: [],
});

// This function will return an Arcjet instance with the latest rules
const arcjet = async () => {
// Get the latest configuration from LaunchDarkly
const newConfig = await getArcjetConfig();
bendechrai marked this conversation as resolved.
Show resolved Hide resolved

// Return the Arcjet instance with the latest rules
return aj.withRule(shield({ mode: currentConfig.shieldMode })).withRule(
slidingWindow({
mode: currentConfig.slidingWindowMode,
max: currentConfig.slidingWindowMax,
interval: currentConfig.slidingWindowInterval,
})
);
bendechrai marked this conversation as resolved.
Show resolved Hide resolved
};

export default arcjet;
bendechrai marked this conversation as resolved.
Show resolved Hide resolved
37 changes: 37 additions & 0 deletions examples/nodejs-express-launchdarkly/lib/launchdarkly.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import * as ld from "@launchdarkly/node-server-sdk";

// Initialize LaunchDarkly client
const client = ld.init(process.env.LAUNCHDARKLY_SDK_KEY);

export const getArcjetConfig = async () => {
// Wait for the LaunchDarkly client to be initialized
await client.waitForInitialization({ timeout: 500 });

// Set the user context for LaunchDarkly - in this example, every user is treated the same.
const context = { key: "guest" };

// Get the latest configuration from LaunchDarkly
const shieldMode = await client.variation("shieldMode", context, "LIVE");
bendechrai marked this conversation as resolved.
Show resolved Hide resolved
const slidingWindowMode = await client.variation(
"slidingWindowMode",
context,
"LIVE"
);
const slidingWindowMax = await client.variation(
"slidingWindowMax",
context,
60
);
const slidingWindowInterval = await client.variation(
"slidingWindowInterval",
context,
60
);

return {
shieldMode,
slidingWindowMode,
slidingWindowMax,
slidingWindowInterval,
};
};
Loading
Loading