diff --git a/docs/articles/hooks.md b/docs/articles/hooks.md
new file mode 100644
index 00000000..d6f22d6d
--- /dev/null
+++ b/docs/articles/hooks.md
@@ -0,0 +1,89 @@
+---
+title: Request/Response Hooks
+---
+
+Hooks allow running code as part of the Request/Response pipeline. Hooks are
+typically added using a plugin as shown in this document, but they can also be
+added globally using [Runtime Extensions](./runtime-extensions.md).
+
+:::tip
+
+All hooks can be either synchronous or asynchronous. To make your hook
+asynchronous simply add the `async` keyword on the function.
+
+:::
+
+### Hook: OnResponseSending
+
+The `OnResponseSending` hook on `ZuploContext` fires just before the response is
+sent to the client. The `Response` can be modified by returning a new `Response`
+from this hook. This hook is useful for creating an inbound policy that also
+needs to run some logic after the response is returned from the handler.
+
+The example below shows a simple tracing policy that adds a trace header to the
+request and ensures the same header is returned with the response.
+
+```ts
+import { ZuploContext, ZuploRequest } from "@zuplo/runtime";
+
+export async function tracingPlugin(
+ request: ZuploRequest,
+ context: ZuploContext,
+ policyName: string,
+) {
+ // Get the trace header
+ let traceparent = request.headers.get("traceparent");
+
+ // If not set, add the header to the request
+ if (!traceparent) {
+ traceparent = crypto.randomUUID();
+ const headers = new Headers(request.headers);
+ headers.set("traceparent", traceparent);
+ return new Request(request, { headers });
+ }
+
+ context.addResponseSendingHook((response, latestRequest, context) => {
+ // If the response doesn't have the trace header that matches, set it
+ if (response.headers.get("traceparent") !== traceparent) {
+ const headers = new Headers(response.headers);
+ headers.set("traceparent", traceparent);
+ return new Response(response.body, {
+ headers,
+ });
+ }
+ return response;
+ });
+
+ return request;
+}
+```
+
+### Hook: OnResponseSendingFinal
+
+The `OnResponseSendingFinal` hook on `ZuploContext` fires immediately after the
+response is sent to the client. The `Response` in this hook is immutable and the
+body has been used. This hook is useful for custom performing various tasks like
+logging or analytics.
+
+```ts
+import { ZuploContext, ZuploRequest } from "@zuplo/runtime";
+
+export async function pluginWithHook(
+ request: ZuploRequest,
+ context: ZuploContext,
+ policyName: string,
+) {
+ const cloned = request.clone();
+ context.addResponseSendingFinalHook(
+ async (response, latestRequest, context) => {
+ const body = await cloned.text();
+ await fetch("https://example.com", {
+ method: "GET",
+ body,
+ });
+ },
+ );
+
+ return request;
+}
+```
diff --git a/docs/articles/runtime-extensions.md b/docs/articles/runtime-extensions.md
index 709fdd38..61d6ecfc 100644
--- a/docs/articles/runtime-extensions.md
+++ b/docs/articles/runtime-extensions.md
@@ -33,7 +33,7 @@ function signature.
The following configurations are available.
-### Custom Problem (Error) Response Formatter
+## Custom Problem (Error) Response Formatter
Zuplo includes built-in error handling that returns errors in the format of the
[Problem Details for HTTP APIs](http://httpproblems.com/) proposed standard.
@@ -85,64 +85,75 @@ export function runtimeInit(runtime: RuntimeExtensions) {
}
```
-### Hook: OnResponseSending
+## Hooks
-The `OnResponseSending` hook allows modification of the `Response` immediately
-before it is sent to the client. The hook provides the `Request` and `Response`
-and returns a `Response`. To modify the outgoing response create and return a
-`new Response()`.
+Hooks allow code to be run as part of the request/response pipeline. Hooks can
+be created at the API level in `zuplo.runtime.ts` as shown below or can be added
+via a plugin as [documented here](./hooks.md).
-The example below shows modifying the response for a specific path.
+:::tip
+
+All hooks can be either synchronous or asynchronous. To make your hook
+asynchronous simply add the `async` keyword on the function.
+
+:::
+
+The following hooks can be set globally in the `zuplo.runtime.ts`:
+
+### Hook: OnRequest
+
+Runs when a request is received, before any plugins or handlers.
+
+```ts
+import { RuntimeExtensions } from "@zuplo/runtime";
+
+export function runtimeInit(runtime: RuntimeExtensions) {
+ runtime.addRequestHook((request, context) => {
+ // Code here
+
+ // Can return a request or a response. If a response is returned the
+ // pipeline stops and the response is returned.
+ return request;
+ });
+}
+```
+
+### Hooks: OnResponseSending
+
+Runs before a response is sent. Response can be modified.
+[More details.](/docs/articles/hooks#hook-onresponsesending)
```ts
import { RuntimeExtensions } from "@zuplo/runtime";
export function runtimeInit(runtime: RuntimeExtensions) {
- runtime.addResponseSendingHook(async (response, latestRequest, context) => {
- const url = new URL(request.url);
- if (url.pathname === "/path-to-change-response") {
- return new Response("New Response", response);
- }
+ runtime.addResponseSendingHook((response, request, context) => {
+ // Code here
return response;
});
}
```
-### Hook: Context OnResponseSendingFinal
+### Hooks: OnResponseSendingFinal
-The `OnResponseSendingFinal` hook on `ZuploContext` fires immediately after the
-response is sent to the client. The `Response` in this hook is immutable and the
-body has been used. This hook is useful for custom performing various tasks like
-logging or analytics.
+Runs before a response is sent. The response cannot be modified.
+[More details.](/docs/articles/hooks#hook-onresponsesendingfinal)
```ts
-import { ZuploContext, ZuploRequest } from "@zuplo/runtime";
-
-export async function pluginWithHook(
- request: ZuploRequest,
- context: ZuploContext,
- policyName: string,
-) {
- const cloned = request.clone();
- context.addResponseSendingFinalHook(
- async (response, latestRequest, context) => {
- const body = await cloned.text();
- await fetch("https://example.com", {
- method: "GET",
- body,
- });
- },
- );
+import { RuntimeExtensions } from "@zuplo/runtime";
- return request;
+export function runtimeInit(runtime: RuntimeExtensions) {
+ runtime.addResponseSendingFinalHook((response, request, context) => {
+ // Code here
+ });
}
```
## Plugin and Handler Extensions
-Built-in and custom plugins and handlers can expose their own extensibility. The
-[AWS Lambda handler](../handlers/aws-lambda.md) exposes the ability to customize
-the event that is sent when invoking the Lambda function.
+Built-in and custom plugins and handlers can expose their own extensibility. For
+example, [AWS Lambda handler](../handlers/aws-lambda.md) exposes the ability to
+customize the event that is sent when invoking the Lambda function.
The example below shows how to use a route's custom property to set the path on
the outgoing event to a custom value.
diff --git a/docs/articles/step-1-setup-basic-gateway.md b/docs/articles/step-1-setup-basic-gateway.md
index 2bb446b7..53c548b7 100644
--- a/docs/articles/step-1-setup-basic-gateway.md
+++ b/docs/articles/step-1-setup-basic-gateway.md
@@ -12,6 +12,13 @@ the key is provided in the unauthenticated error message.
To get started, sign in to [portal.zuplo.com](https://portal.zuplo.com) and
create a free account. Create a new **empty** project. Then...
+:::tip Local Development
+
+Zuplo also supports building and running your API locally. To learn more
+[see the documentation](./local-development.md).
+
+:::
+
## 1/ Add a route
Inside your new project, choose the `routes.oas.json` file and click **Add
@@ -33,9 +40,10 @@ Save your changes (you can click the disk icon next to `routes.oas.json` or
press CMD+S).
You can quickly test this route by clicking the **Test** button next to the
-**Path** field and clicking the URL in the dialog that opens.
+**Path** field. You can use the built in test tool or click the URL to open in a
+new tab.
-![Test](https://cdn.zuplo.com/assets/cd094b3c-efbe-4c2b-995c-60ce0302704a.png)
+
You should receive a 401 Unauthorized that says something similar to
diff --git a/docs/articles/step-2-add-api-key-auth.md b/docs/articles/step-2-add-api-key-auth.md
index cc3917dc..8c3f8a63 100644
--- a/docs/articles/step-2-add-api-key-auth.md
+++ b/docs/articles/step-2-add-api-key-auth.md
@@ -28,9 +28,10 @@ JSON. Note,
The API key auth policy should usually be one of the first policies in your
request pipeline, drag it to the top if you have multiple policies.
-
:::
+
+
If you test your route, you should get a 401 Unauthorized response
```
@@ -68,10 +69,10 @@ Since we need to send the key in a header, it's hard to use the browser for this
test. We'll use our built in test client in Zuplo but you could also use Postman
for this part.
-Go to the API Test Console and create a new **Manual Test**. Set the **path** to
-`/todos` and hit **Test**.
+Next to the path of your route in Route Designer click the **Test** button. Set
+the **path** to `/todos` and hit **Test**.
-![Test Console](https://cdn.zuplo.com/assets/611050a5-257e-4594-a914-1da68d504371.png)
+
You should get a 401 Unauthorized response. Add an new `authorization` header
with the value `Bearer YOUR_API_KEY` and insert the API Key you got from the
@@ -79,14 +80,14 @@ developer portal.
You should now get a 200 OK.
-![200 OK](https://cdn.zuplo.com/assets/82823f9d-62ad-4f18-9e07-bb89dc9ad32d.png)
+
:::note
We also offer an API for our API key service that allows you to programmatically
create consumers and even create your own developer portal or integrate key
-management into your existing dashboard. Contact us at `support@zuplo.com` for
-access.
+management into your existing dashboard. See
+[this document for details](./api-key-api.md).
:::
diff --git a/sidebars.js b/sidebars.js
index cccf56a4..e0e4bb10 100644
--- a/sidebars.js
+++ b/sidebars.js
@@ -310,6 +310,7 @@ const sidebars = {
"articles/runtime-behaviors",
"articles/zp-body-removed",
"articles/audit-log",
+ "articles/hooks",
"articles/runtime-extensions",
"articles/not-found-handler",
],