Skip to content

Commit

Permalink
chore: Explain custom Express request details (#90)
Browse files Browse the repository at this point in the history
* chore: Explain custom Express request details

* Address comments
  • Loading branch information
davidmytton authored Sep 25, 2024
1 parent ef0b83f commit 69ccde1
Show file tree
Hide file tree
Showing 4 changed files with 89 additions and 4 deletions.
1 change: 1 addition & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"@arcjet/decorate": "1.0.0-alpha.26",
"@arcjet/env": "1.0.0-alpha.26",
"@arcjet/eslint-config": "1.0.0-alpha.26",
"@arcjet/headers": "^1.0.0-alpha.26",
"@arcjet/next": "1.0.0-alpha.26",
"@arcjet/node": "1.0.0-alpha.26",
"@arcjet/protocol": "1.0.0-alpha.26",
Expand Down
20 changes: 16 additions & 4 deletions src/content/docs/troubleshooting.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ description: "Debugging common problems and errors with Arcjet."
import Comments from "/src/components/Comments.astro";

import { Tabs, TabItem, Code } from "@astrojs/starlight/components";
import RequestFieldsExpressTS from "/src/snippets/reference/nodejs/RequestFieldsExpress.ts?raw";
import RequestFieldsTS from "/src/snippets/reference/nodejs/RequestFields.ts?raw";
import RequestFieldsHonoNodeTS from "/src/snippets/reference/nodejs/RequestFieldsHonoNode.ts?raw";

Expand Down Expand Up @@ -105,22 +106,33 @@ The required fields are:
the correct IP which you can also use directly.
- `method` - The HTTP method of the request.
- `host` - The host of the request.
- `path` - The path of the request.
- `path` or `url` - The path of the request. In the Node.js SDK this field is
called `url`. If you are not using an adapter, this is called `path`.
- `headers` - The headers of the request.

Here are some examples of how to provide these fields:

<Tabs>
<TabItem label="Node.js + Express">
This example uses a Node.js + Express server using the Arcjet root package
without an adapter. We do not recommend this approach as it requires you to
manually provide the request. Instead, [use the Node.js
adapter](/get-started/nodejs-express).
<Code code={RequestFieldsExpressTS} lang="ts" />
</TabItem>
<TabItem label="Node.js">
This example uses a pure Node.js server:
<Code code={RequestFieldsTS} lang="js" />
This example uses a pure Node.js server using the Arcjet root package
without an adapter. We do not recommend this approach as it requires you to
manually provide the request. Instead, [use the Node.js
adapter](/get-started/nodejs-express).
<Code code={RequestFieldsTS} lang="ts" />
</TabItem>
<TabItem label="Hono + Node.js">
This example uses the [Hono framework with
Node.js](https://hono.dev/getting-started/nodejs). It requires a bit more
manual setup until we officially support Hono, see
[arcjet-js#477](https://github.com/arcjet/arcjet-js/issues/477).
<Code code={RequestFieldsHonoNodeTS} lang="js" />
<Code code={RequestFieldsHonoNodeTS} lang="ts" />
</TabItem>
</Tabs>

Expand Down
71 changes: 71 additions & 0 deletions src/snippets/reference/nodejs/RequestFieldsExpress.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { readBody } from "@arcjet/body";
import ArcjetHeaders from "@arcjet/headers";
import arcjet, { fixedWindow, shield } from "arcjet";
import express from "express";

const app = express();
const port = 3000;

const aj = arcjet({
// Get your site key from https://app.arcjet.com and set it as an environment
// variable rather than hard coding.
key: process.env.ARCJET_KEY!,
// Limiting by ip.src is the default if not specified characteristics:
//["ip.src"],
rules: [
// Protect against common attacks with Arcjet Shield
shield({
mode: "LIVE", // will block requests. Use "DRY_RUN" to log only
}),
// Fixed window rate limit. Arcjet also supports sliding window and token
// bucket.
fixedWindow({
mode: "LIVE", // will block requests. Use "DRY_RUN" to log only
window: "1m", // 1 min fixed window
max: 1, // allow a single request (for demo purposes)
}),
],
});

app.get("/", async (req, res) => {
// If you are using standard Express, you can just pass req directly to
// protect(). If you want to do something custom, you can construct an object
// with the necessary details, such as the following:
const headers = new ArcjetHeaders(req.headers);
const details = {
ip: req.ip,
method: req.method,
host: req.headers.host,
path: req.path,
headers,
};

// Provide a function to get the body in case it is needed by a rule.
const getBody = async () => {
try {
return await readBody(req, {
// Only read up to 1 MiB of the body
limit: 1048576,
});
} catch {
// Return undefined in case of failure to allow the rule that called
// `getBody` handle it.
return undefined;
}
};

const decision = await aj.protect({ getBody }, details);
console.log(decision);

if (decision.isDenied()) {
res.writeHead(429, { "Content-Type": "application/json" });
res.end(JSON.stringify({ error: "Too Many Requests" }));
} else {
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify({ message: "Hello World" }));
}
});

app.listen(port, () => {
console.log(`Example app listening on port ${port}`);
});

0 comments on commit 69ccde1

Please sign in to comment.