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", ],