zuplo 6.70.68 → 6.70.70
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/docs/ai-gateway/getting-started.mdx +12 -8
- package/docs/ai-gateway/introduction.mdx +11 -9
- package/docs/articles/api-key-buckets.mdx +4 -2
- package/docs/articles/archiving-requests-to-storage.mdx +4 -4
- package/docs/articles/branch-based-deployments.mdx +10 -8
- package/docs/articles/ci-cd-github/cleanup-on-branch-delete.mdx +52 -31
- package/docs/articles/ci-cd-github/pr-preview-environments.mdx +17 -6
- package/docs/articles/custom-ci-cd-azure.mdx +1 -1
- package/docs/articles/custom-ci-cd-bitbucket.mdx +1 -1
- package/docs/articles/custom-ci-cd-circleci.mdx +1 -1
- package/docs/articles/custom-ci-cd-github.mdx +1 -1
- package/docs/articles/custom-ci-cd-gitlab.mdx +1 -1
- package/docs/articles/graphql.mdx +276 -0
- package/docs/articles/monorepo-deployment.mdx +17 -3
- package/docs/articles/opentelemetry.mdx +5 -2
- package/docs/articles/per-user-rate-limits-using-db.mdx +5 -6
- package/docs/articles/securing-the-gateway-with-client-mtls.mdx +68 -43
- package/docs/articles/step-1-setup-basic-gateway.mdx +1 -3
- package/docs/articles/step-2-add-rate-limiting.mdx +1 -1
- package/docs/articles/testing.mdx +1 -1
- package/docs/articles/troubleshooting.md +7 -3
- package/docs/articles/waf-ddos-akamai.md +35 -16
- package/docs/articles/waf-ddos-aws-waf-shield.mdx +35 -16
- package/docs/articles/waf-ddos-fastly.mdx +10 -7
- package/docs/cli/deploy.mdx +13 -10
- package/docs/cli/deploy.partial.mdx +13 -10
- package/docs/dev-portal/zudoku/components/sidecar-box.mdx +131 -0
- package/docs/dev-portal/zudoku/configuration/api-catalog.md +62 -42
- package/docs/dev-portal/zudoku/configuration/api-reference.md +5 -4
- package/docs/dev-portal/zudoku/configuration/navigation.mdx +70 -7
- package/docs/guides/canary-routing-for-employees.mdx +103 -39
- package/docs/guides/modify-openapi-paths.mdx +3 -3
- package/docs/handlers/legacy-dev-portal-handler.mdx +1 -1
- package/docs/handlers/mcp-server.mdx +13 -11
- package/docs/handlers/url-forward.mdx +5 -1
- package/docs/handlers/url-rewrite.mdx +7 -2
- package/docs/handlers/websocket-handler.mdx +5 -1
- package/docs/mcp-gateway/observability/logging.mdx +19 -12
- package/docs/mcp-server/resources.mdx +27 -15
- package/docs/mcp-server/testing.mdx +0 -2
- package/docs/policies/archive-request-azure-storage-inbound/doc.md +1 -1
- package/docs/policies/archive-response-azure-storage-outbound/doc.md +1 -1
- package/docs/policies/ip-restriction-inbound/policy.ts +1 -1
- package/docs/programmable-api/http-problems.mdx +0 -18
- package/docs/programmable-api/jwt-service-plugin.mdx +131 -109
- package/docs/programmable-api/runtime-behaviors.mdx +4 -2
- package/docs/programmable-api/streaming-zone-cache.mdx +4 -6
- package/docs/programmable-api/web-crypto-apis.mdx +10 -6
- package/package.json +4 -4
- package/docs/errors/get-head-body-error.mdx +0 -41
|
@@ -9,7 +9,7 @@ initial configuration to making your first LLM request through Zuplo.
|
|
|
9
9
|
## Prerequisites
|
|
10
10
|
|
|
11
11
|
- A Zuplo account (sign up free at [zuplo.com](https://zuplo.com))
|
|
12
|
-
- API keys for at least one LLM provider (OpenAI, Anthropic, Google
|
|
12
|
+
- API keys for at least one LLM provider (OpenAI, Anthropic, Google, Mistral,
|
|
13
13
|
etc.)
|
|
14
14
|
- An application that needs to call LLM APIs
|
|
15
15
|
|
|
@@ -27,9 +27,9 @@ includes Apps, Teams, and a setup guide to help you get started.
|
|
|
27
27
|
|
|
28
28
|
## Step 2: Configure Providers
|
|
29
29
|
|
|
30
|
-
Providers are the LLM services (like OpenAI or
|
|
31
|
-
|
|
32
|
-
|
|
30
|
+
Providers are the LLM services (like OpenAI or Anthropic) that your applications
|
|
31
|
+
will use. You'll configure these once as an administrator, and your team members
|
|
32
|
+
can use them without needing direct access to provider API keys.
|
|
33
33
|
|
|
34
34
|
### Adding Your First Provider
|
|
35
35
|
|
|
@@ -43,13 +43,17 @@ team members can use them without needing direct access to provider API keys.
|
|
|
43
43
|
### Adding Additional Providers
|
|
44
44
|
|
|
45
45
|
Repeat the process above to add more providers. This allows your teams to switch
|
|
46
|
-
between providers (OpenAI,
|
|
46
|
+
between providers (OpenAI, Anthropic, etc.) without changing application code.
|
|
47
47
|
|
|
48
48
|
**Example providers you might add:**
|
|
49
49
|
|
|
50
50
|
- OpenAI (for GPT models)
|
|
51
|
-
-
|
|
52
|
-
-
|
|
51
|
+
- Anthropic (for Claude models)
|
|
52
|
+
- Google (for Gemini models)
|
|
53
|
+
- Mistral (for Mistral models)
|
|
54
|
+
|
|
55
|
+
See [AI Providers](./providers.mdx) for the full list of supported providers and
|
|
56
|
+
capabilities, including OpenAI-compatible custom providers.
|
|
53
57
|
|
|
54
58
|
## Step 3: Create a Team
|
|
55
59
|
|
|
@@ -202,7 +206,7 @@ Now that your AI Gateway is running, explore additional features:
|
|
|
202
206
|
### Switch Providers Without Code Changes
|
|
203
207
|
|
|
204
208
|
1. Go to your app settings
|
|
205
|
-
2. Change the **Provider** dropdown (for example, from OpenAI to
|
|
209
|
+
2. Change the **Provider** dropdown (for example, from OpenAI to Anthropic)
|
|
206
210
|
3. Select a new model
|
|
207
211
|
4. Click **Save Changes**
|
|
208
212
|
|
|
@@ -4,16 +4,17 @@ sidebar_label: Introduction
|
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
Zuplo's AI Gateway acts as an intelligent proxy layer that sits between your
|
|
7
|
-
engineering team's applications and LLM providers like OpenAI,
|
|
8
|
-
and
|
|
9
|
-
providers, all requests flow through the Zuplo AI Gateway, which streams
|
|
7
|
+
engineering team's applications and LLM providers like OpenAI, Anthropic,
|
|
8
|
+
Google, and Mistral. Instead of your applications communicating directly with
|
|
9
|
+
these providers, all requests flow through the Zuplo AI Gateway, which streams
|
|
10
10
|
responses while applying policies, controls, and monitoring.
|
|
11
11
|
|
|
12
12
|
## Key Benefits
|
|
13
13
|
|
|
14
|
-
**Provider Independence**: Switch between LLM providers (OpenAI,
|
|
15
|
-
|
|
16
|
-
choice through the gateway rather than hard coding it
|
|
14
|
+
**Provider Independence**: Switch between LLM providers (OpenAI, Anthropic,
|
|
15
|
+
Google, Mistral, and more) dynamically without modifying application code.
|
|
16
|
+
Configure your provider choice through the gateway rather than hard coding it
|
|
17
|
+
into your applications.
|
|
17
18
|
|
|
18
19
|
**Cost Control**: Set spending limits at organization, team, and application
|
|
19
20
|
levels with hierarchical budgets that cascade down through your structure.
|
|
@@ -49,9 +50,10 @@ credentials.
|
|
|
49
50
|
### Multi-Provider Support
|
|
50
51
|
|
|
51
52
|
Configure multiple LLM providers within a single gateway project. Supported
|
|
52
|
-
providers include OpenAI
|
|
53
|
-
|
|
54
|
-
|
|
53
|
+
providers include OpenAI, Anthropic, Google, Mistral, and OpenAI-compatible
|
|
54
|
+
custom providers. See [AI Providers](./providers.mdx) for the full list of
|
|
55
|
+
providers and supported capabilities. Select which models are available to your
|
|
56
|
+
teams when configuring each provider.
|
|
55
57
|
|
|
56
58
|
### Team Hierarchy & Budgets
|
|
57
59
|
|
|
@@ -11,10 +11,12 @@ credentials across different environments. Learn more in the
|
|
|
11
11
|
|
|
12
12
|
Zuplo automatically creates three buckets for each project:
|
|
13
13
|
|
|
14
|
-
- **Working copy**: Stores API keys for the working-copy environment
|
|
15
14
|
- **Production**: Stores API keys for the production environment (your default
|
|
16
15
|
Git branch)
|
|
17
|
-
- **
|
|
16
|
+
- **Preview**: Stores API keys shared across all preview environments
|
|
17
|
+
(non-default Git branches)
|
|
18
|
+
- **Development**: Stores API keys for the development (working copy)
|
|
19
|
+
environment
|
|
18
20
|
|
|
19
21
|
For more information on how environments relate to Git branches, see
|
|
20
22
|
[Branch-Based Deployments](./branch-based-deployments.mdx).
|
|
@@ -30,7 +30,7 @@ can generate one of these on the `Shared access tokens` tab.
|
|
|
30
30
|
Note, you should minimize the permissions - and select only the `Create`
|
|
31
31
|
permission. Choose a sensible start and expiration time for your token. Note, we
|
|
32
32
|
don't recommend restricting IP addresses because Zuplo runs at the edge in over
|
|
33
|
-
|
|
33
|
+
300 data centers worldwide.
|
|
34
34
|
|
|
35
35
|

|
|
36
36
|
|
|
@@ -46,7 +46,7 @@ You'll need another environment variable called `BLOB_CONTAINER_PATH`.
|
|
|
46
46
|
We'll write a policy called `request-archive-policy` that can be used on all
|
|
47
47
|
routes.
|
|
48
48
|
|
|
49
|
-
```ts title="
|
|
49
|
+
```ts title="modules/request-archive-policy.ts"
|
|
50
50
|
import { ZuploRequest, ZuploContext } from "@zuplo/runtime";
|
|
51
51
|
|
|
52
52
|
export type RequestArchivePolicyOptions = {
|
|
@@ -102,7 +102,7 @@ example below:
|
|
|
102
102
|
"policyType": "code-policy",
|
|
103
103
|
"handler": {
|
|
104
104
|
"export": "default",
|
|
105
|
-
"module": "$import(./modules/archive-
|
|
105
|
+
"module": "$import(./modules/request-archive-policy)",
|
|
106
106
|
"options": {
|
|
107
107
|
"blobCreateSas": "$env(BLOB_CREATE_SAS)",
|
|
108
108
|
"blobContainerPath": "$env(BLOB_CONTAINER_PATH)"
|
|
@@ -111,7 +111,7 @@ example below:
|
|
|
111
111
|
}
|
|
112
112
|
```
|
|
113
113
|
|
|
114
|
-
Don't forget to reference the `
|
|
114
|
+
Don't forget to reference the `request-archive-policy` in the policies.inbound
|
|
115
115
|
property of your routes.
|
|
116
116
|
|
|
117
117
|
Here's the policy in action:
|
|
@@ -34,17 +34,19 @@ bugfix/123 ─────────────────► Preview (b
|
|
|
34
34
|
When Zuplo deploys an environment from a branch, the environment name matches
|
|
35
35
|
the branch name. For example:
|
|
36
36
|
|
|
37
|
-
| Branch Name | Environment Name | Example URL
|
|
38
|
-
| -------------- | ---------------- |
|
|
39
|
-
| `main` | main | `https://my-project-main-abc1234.zuplo.app`
|
|
40
|
-
| `staging` | staging | `https://my-project-staging-def5678.zuplo.app`
|
|
41
|
-
| `feature/auth` | feature/auth | `https://my-project-feature-
|
|
37
|
+
| Branch Name | Environment Name | Example URL |
|
|
38
|
+
| -------------- | ---------------- | ------------------------------------------------- |
|
|
39
|
+
| `main` | main | `https://my-project-main-abc1234.zuplo.app` |
|
|
40
|
+
| `staging` | staging | `https://my-project-staging-def5678.zuplo.app` |
|
|
41
|
+
| `feature/auth` | feature/auth | `https://my-project-feature-au-ghi9012.zuplo.app` |
|
|
42
42
|
|
|
43
43
|
:::note
|
|
44
44
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
45
|
+
Zuplo normalizes the branch name in the deployment URL — lowercasing it,
|
|
46
|
+
replacing special characters with hyphens, and truncating it to 10 characters —
|
|
47
|
+
which is why `feature/auth` appears as `feature-au`. The URL also includes a
|
|
48
|
+
unique identifier to ensure each deployment has a distinct address. Configure
|
|
49
|
+
[custom domains](./custom-domains.mdx) for your environments.
|
|
48
50
|
|
|
49
51
|
:::
|
|
50
52
|
|
|
@@ -34,24 +34,29 @@ jobs:
|
|
|
34
34
|
run: |
|
|
35
35
|
# The deleted branch name
|
|
36
36
|
BRANCH_NAME="${{ github.event.ref }}"
|
|
37
|
-
#
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
echo "
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
--
|
|
46
|
-
--
|
|
37
|
+
# Deployment names use the format {project}-{branch}-{hash}, where
|
|
38
|
+
# the branch segment is normalized (lowercase, special characters
|
|
39
|
+
# replaced by hyphens) and truncated to 10 characters
|
|
40
|
+
ENV_NAME=$(echo "$BRANCH_NAME" | tr '/_' '--' |
|
|
41
|
+
tr '[:upper:]' '[:lower:]' | cut -c1-10 | sed 's/-*$//')
|
|
42
|
+
|
|
43
|
+
# Find the deployment for the deleted branch and delete it by URL
|
|
44
|
+
npx zuplo list --api-key "$ZUPLO_API_KEY" \
|
|
45
|
+
--output json --show-details |
|
|
46
|
+
jq -r --arg env "$ENV_NAME" \
|
|
47
|
+
'.[] | select(.name | test("-" + $env + "-[a-z0-9]+$")) | .url' |
|
|
48
|
+
while read -r URL; do
|
|
49
|
+
echo "Deleting environment: $URL"
|
|
50
|
+
npx zuplo delete --url "$URL" --api-key "$ZUPLO_API_KEY" --wait || true
|
|
51
|
+
done
|
|
47
52
|
```
|
|
48
53
|
|
|
49
54
|
This workflow:
|
|
50
55
|
|
|
51
56
|
1. Triggers when any branch is deleted
|
|
52
|
-
2. Converts the branch name to the
|
|
53
|
-
3.
|
|
54
|
-
4. Continues without error if
|
|
57
|
+
2. Converts the branch name to the deployment-name branch segment format
|
|
58
|
+
3. Looks up the matching deployment and deletes it by URL
|
|
59
|
+
4. Continues without error if no matching environment exists
|
|
55
60
|
|
|
56
61
|
## Combining with PR Cleanup
|
|
57
62
|
|
|
@@ -95,24 +100,40 @@ jobs:
|
|
|
95
100
|
|
|
96
101
|
- name: Cleanup stale environments
|
|
97
102
|
run: |
|
|
98
|
-
# Get all remote branches
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
103
|
+
# Get all remote branches, converted to the deployment-name branch
|
|
104
|
+
# segment format: lowercase, special characters replaced by hyphens,
|
|
105
|
+
# truncated to 10 characters
|
|
106
|
+
BRANCHES=$(git branch -r | sed 's|origin/||' | tr -d ' ' |
|
|
107
|
+
tr '/_' '--' | tr '[:upper:]' '[:lower:]' |
|
|
108
|
+
cut -c1-10 | sed 's/-*$//')
|
|
109
|
+
|
|
110
|
+
# List deployed environments as JSON. Each entry has the shape
|
|
111
|
+
# {"projectName": "...", "name": "...", "url": "..."}
|
|
112
|
+
npx zuplo list --api-key "$ZUPLO_API_KEY" \
|
|
113
|
+
--output json --show-details > environments.json
|
|
114
|
+
|
|
115
|
+
# Deployment names follow the format {project}-{branch}-{hash}
|
|
116
|
+
jq -r '.[] | "\(.name) \(.url)"' environments.json |
|
|
117
|
+
while read -r NAME URL; do
|
|
118
|
+
# Skip protected environments
|
|
119
|
+
case "$NAME" in
|
|
120
|
+
*-main-* | *-production-* | *-staging-*) continue ;;
|
|
121
|
+
esac
|
|
122
|
+
|
|
123
|
+
# Delete only if no branch matches the deployment name
|
|
124
|
+
STALE=true
|
|
125
|
+
for BRANCH in $BRANCHES; do
|
|
126
|
+
if [[ "$NAME" == *"-$BRANCH-"* ]]; then
|
|
127
|
+
STALE=false
|
|
128
|
+
break
|
|
129
|
+
fi
|
|
130
|
+
done
|
|
131
|
+
|
|
132
|
+
if [[ "$STALE" == "true" ]]; then
|
|
133
|
+
echo "Deleting stale environment: $NAME"
|
|
134
|
+
npx zuplo delete --url "$URL" --api-key "$ZUPLO_API_KEY" --wait || true
|
|
135
|
+
fi
|
|
136
|
+
done
|
|
116
137
|
```
|
|
117
138
|
|
|
118
139
|
## Next Steps
|
|
@@ -71,15 +71,26 @@ jobs:
|
|
|
71
71
|
- name: Install dependencies
|
|
72
72
|
run: npm install
|
|
73
73
|
|
|
74
|
+
- name: Get deployment URL
|
|
75
|
+
id: get-url
|
|
76
|
+
uses: actions/github-script@v7
|
|
77
|
+
with:
|
|
78
|
+
script: |
|
|
79
|
+
const comments = await github.rest.issues.listComments({
|
|
80
|
+
issue_number: context.issue.number,
|
|
81
|
+
owner: context.repo.owner,
|
|
82
|
+
repo: context.repo.repo,
|
|
83
|
+
});
|
|
84
|
+
const match = comments.data
|
|
85
|
+
.map((c) => c.body.match(/Deployed to: (https:\/\/\S+)/))
|
|
86
|
+
.find(Boolean);
|
|
87
|
+
core.setOutput("url", match ? match[1] : "");
|
|
88
|
+
|
|
74
89
|
- name: Delete environment
|
|
90
|
+
if: steps.get-url.outputs.url != ''
|
|
75
91
|
run: |
|
|
76
|
-
# Environment name is based on branch name
|
|
77
|
-
BRANCH_NAME="${{ github.head_ref }}"
|
|
78
|
-
# Convert slashes to hyphens (Zuplo convention)
|
|
79
|
-
ENV_NAME="${BRANCH_NAME//\//-}"
|
|
80
|
-
|
|
81
92
|
npx zuplo delete \
|
|
82
|
-
--
|
|
93
|
+
--url "${{ steps.get-url.outputs.url }}" \
|
|
83
94
|
--api-key "$ZUPLO_API_KEY" \
|
|
84
95
|
--wait
|
|
85
96
|
```
|
|
@@ -38,7 +38,7 @@ npx zuplo deploy --api-key "$ZUPLO_API_KEY"
|
|
|
38
38
|
npx zuplo test --endpoint https://your-env.zuplo.dev
|
|
39
39
|
|
|
40
40
|
# Clean up environments you no longer need
|
|
41
|
-
npx zuplo delete --
|
|
41
|
+
npx zuplo delete --url "https://my-api-pr-123-d3vp01.zuplo.app" --api-key "$ZUPLO_API_KEY"
|
|
42
42
|
|
|
43
43
|
# Test locally before deploying
|
|
44
44
|
npx zuplo dev # starts local server on port 9000
|
|
@@ -37,7 +37,7 @@ npx zuplo deploy --api-key "$ZUPLO_API_KEY"
|
|
|
37
37
|
npx zuplo test --endpoint https://your-env.zuplo.dev
|
|
38
38
|
|
|
39
39
|
# Clean up environments you no longer need
|
|
40
|
-
npx zuplo delete --
|
|
40
|
+
npx zuplo delete --url "https://my-api-pr-123-d3vp01.zuplo.app" --api-key "$ZUPLO_API_KEY"
|
|
41
41
|
|
|
42
42
|
# Test locally before deploying
|
|
43
43
|
npx zuplo dev # starts local server on port 9000
|
|
@@ -36,7 +36,7 @@ npx zuplo deploy --api-key "$ZUPLO_API_KEY"
|
|
|
36
36
|
npx zuplo test --endpoint https://your-env.zuplo.dev
|
|
37
37
|
|
|
38
38
|
# Clean up environments you no longer need
|
|
39
|
-
npx zuplo delete --
|
|
39
|
+
npx zuplo delete --url "https://my-api-pr-123-d3vp01.zuplo.app" --api-key "$ZUPLO_API_KEY"
|
|
40
40
|
|
|
41
41
|
# Test locally before deploying
|
|
42
42
|
npx zuplo dev # starts local server on port 9000
|
|
@@ -54,7 +54,7 @@ npx zuplo deploy --api-key "$ZUPLO_API_KEY"
|
|
|
54
54
|
npx zuplo test --endpoint https://your-env.zuplo.dev
|
|
55
55
|
|
|
56
56
|
# Clean up environments you no longer need
|
|
57
|
-
npx zuplo delete --
|
|
57
|
+
npx zuplo delete --url "https://my-api-pr-123-d3vp01.zuplo.app" --api-key "$ZUPLO_API_KEY"
|
|
58
58
|
|
|
59
59
|
# Test locally before deploying
|
|
60
60
|
npx zuplo dev # starts local server on port 9000
|
|
@@ -36,7 +36,7 @@ npx zuplo deploy --api-key "$ZUPLO_API_KEY"
|
|
|
36
36
|
npx zuplo test --endpoint https://your-env.zuplo.dev
|
|
37
37
|
|
|
38
38
|
# Clean up environments you no longer need
|
|
39
|
-
npx zuplo delete --
|
|
39
|
+
npx zuplo delete --url "https://my-api-pr-123-d3vp01.zuplo.app" --api-key "$ZUPLO_API_KEY"
|
|
40
40
|
|
|
41
41
|
# Test locally before deploying
|
|
42
42
|
npx zuplo dev # starts local server on port 9000
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: GraphQL on Zuplo
|
|
3
|
+
sidebar_label: GraphQL
|
|
4
|
+
description:
|
|
5
|
+
Proxy a GraphQL API through your Zuplo gateway and publish a browsable schema
|
|
6
|
+
reference with a playground in your Dev Portal.
|
|
7
|
+
tags:
|
|
8
|
+
- backends
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
Zuplo fronts GraphQL APIs the same way it fronts REST: requests pass through a
|
|
12
|
+
gateway route — with whatever authentication, rate limiting, and
|
|
13
|
+
GraphQL-specific policies you attach — and your Dev Portal documents the schema.
|
|
14
|
+
This guide covers both halves: adding a GraphQL endpoint to your gateway routes,
|
|
15
|
+
then rendering a schema reference and playground in the Dev Portal with
|
|
16
|
+
`@zudoku/plugin-graphql`.
|
|
17
|
+
|
|
18
|
+
## Prerequisites
|
|
19
|
+
|
|
20
|
+
- A Zuplo project
|
|
21
|
+
- An upstream GraphQL API to proxy
|
|
22
|
+
- For the Dev Portal section: your project's
|
|
23
|
+
[Dev Portal](../dev-portal/introduction.mdx) lives in its `docs/` folder. The
|
|
24
|
+
plugin requires the `zudoku` package version `0.80.1` or newer — check the
|
|
25
|
+
version in `docs/package.json` and
|
|
26
|
+
[update the Dev Portal](../dev-portal/updating.mdx) if yours is older.
|
|
27
|
+
|
|
28
|
+
## Add a GraphQL endpoint to your gateway
|
|
29
|
+
|
|
30
|
+
A GraphQL API serves every query and mutation from a single endpoint, so the
|
|
31
|
+
gateway route uses the [URL Rewrite handler](../handlers/url-rewrite.mdx) —
|
|
32
|
+
every request goes to exactly the upstream URL — rather than URL Forward, which
|
|
33
|
+
appends the incoming path.
|
|
34
|
+
|
|
35
|
+
### Add a new GraphQL route
|
|
36
|
+
|
|
37
|
+
<Stepper>
|
|
38
|
+
|
|
39
|
+
1. **Open the Route Designer**
|
|
40
|
+
|
|
41
|
+
In the Zuplo Portal, open the **Code** tab and click **routes.oas.json**.
|
|
42
|
+
This opens the Route Designer, which lists your project's existing routes and
|
|
43
|
+
an **Add** menu.
|
|
44
|
+
|
|
45
|
+
2. **Add the endpoint**
|
|
46
|
+
|
|
47
|
+
Click **Add** and select **GraphQL Endpoint**. This creates a `POST /graphql`
|
|
48
|
+
route preconfigured with the URL Rewrite handler and a demo upstream. If
|
|
49
|
+
`/graphql` is already taken, the Portal appends a short suffix (for example
|
|
50
|
+
`/graphql-b891`) — edit the path to whatever you prefer. GraphQL routes show
|
|
51
|
+
a **GraphQL** badge in the route list instead of an HTTP method.
|
|
52
|
+
|
|
53
|
+
3. **Point it at your upstream**
|
|
54
|
+
|
|
55
|
+
Expand the new route. Its handler is preset to **URL Rewrite** in the
|
|
56
|
+
**Request Handler** drop-down; in the URL text box below it, replace the demo
|
|
57
|
+
URL with your GraphQL API's endpoint, for example
|
|
58
|
+
`https://api.example.com/graphql`. To use a different backend per
|
|
59
|
+
environment, reference an [environment variable](./environment-variables.mdx)
|
|
60
|
+
such as `${env.GRAPHQL_API_URL}`.
|
|
61
|
+
|
|
62
|
+
4. **Save**
|
|
63
|
+
|
|
64
|
+
Save your changes. Your gateway now proxies GraphQL requests at `/graphql`.
|
|
65
|
+
|
|
66
|
+
</Stepper>
|
|
67
|
+
|
|
68
|
+
### Mark an existing route as GraphQL
|
|
69
|
+
|
|
70
|
+
Already proxying a GraphQL API through a regular route? Open the route in the
|
|
71
|
+
Route Designer, click the **⋯** menu at the end of the route's options row (next
|
|
72
|
+
to the CORS and docs toggles), and check **Mark as GraphQL**. This tags the
|
|
73
|
+
route as a GraphQL endpoint without changing its handler or policies.
|
|
74
|
+
|
|
75
|
+
### The resulting route configuration
|
|
76
|
+
|
|
77
|
+
Both paths produce a route in `routes.oas.json` with the `x-graphql` extension
|
|
78
|
+
set:
|
|
79
|
+
|
|
80
|
+
```json title="config/routes.oas.json"
|
|
81
|
+
{
|
|
82
|
+
"paths": {
|
|
83
|
+
"/graphql": {
|
|
84
|
+
"post": {
|
|
85
|
+
"summary": "GraphQL Endpoint",
|
|
86
|
+
"x-graphql": true,
|
|
87
|
+
"x-zuplo-route": {
|
|
88
|
+
"corsPolicy": "none",
|
|
89
|
+
"handler": {
|
|
90
|
+
"export": "urlRewriteHandler",
|
|
91
|
+
"module": "$import(@zuplo/runtime)",
|
|
92
|
+
"options": {
|
|
93
|
+
"rewritePattern": "https://api.example.com/graphql"
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
"policies": {
|
|
97
|
+
"inbound": []
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
"operationId": "graphql-1a2bc3d4"
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
:::tip{title="Secure the endpoint"}
|
|
108
|
+
|
|
109
|
+
GraphQL has its own attack surface — deeply nested queries, expensive
|
|
110
|
+
operations, and schema introspection. Zuplo ships policies for all three. See
|
|
111
|
+
[Secure your GraphQL API](./graphql-security.mdx) to add complexity limits and
|
|
112
|
+
disable introspection in production.
|
|
113
|
+
|
|
114
|
+
:::
|
|
115
|
+
|
|
116
|
+
## Document the API in your Dev Portal
|
|
117
|
+
|
|
118
|
+
The `@zudoku/plugin-graphql` package renders GraphQL APIs in your Dev Portal
|
|
119
|
+
from a schema. Each plugin instance produces a browsable type reference
|
|
120
|
+
(queries, mutations, objects, enums, and so on) plus a playground, generated
|
|
121
|
+
straight from your schema.
|
|
122
|
+
|
|
123
|
+
:::caution{title="Beta"}
|
|
124
|
+
|
|
125
|
+
The GraphQL plugin is in beta. During the beta it isn't available in your
|
|
126
|
+
project's working copy — it only works in projects
|
|
127
|
+
[connected to source control](./source-control.mdx).
|
|
128
|
+
|
|
129
|
+
:::
|
|
130
|
+
|
|
131
|
+
<Stepper>
|
|
132
|
+
|
|
133
|
+
1. **Install the plugin**
|
|
134
|
+
|
|
135
|
+
In your project's `docs/` folder (where `zudoku.config.tsx` lives), install
|
|
136
|
+
the plugin:
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
npm install @zudoku/plugin-graphql
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
The plugin requires `zudoku` `0.80.1` or newer.
|
|
143
|
+
|
|
144
|
+
2. **Add a schema**
|
|
145
|
+
|
|
146
|
+
Drop a GraphQL schema definition language (SDL) file next to your config:
|
|
147
|
+
|
|
148
|
+
```graphql title="schema.graphql"
|
|
149
|
+
type Query {
|
|
150
|
+
product(id: ID!): Product
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
type Product {
|
|
154
|
+
id: ID!
|
|
155
|
+
name: String!
|
|
156
|
+
price: Float!
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
Don't have an SDL file handy? Skip this step and introspect a live endpoint
|
|
161
|
+
at build time with `type: "url"` in the next step — the schema is fetched for
|
|
162
|
+
you when the portal builds.
|
|
163
|
+
|
|
164
|
+
3. **Register the plugin**
|
|
165
|
+
|
|
166
|
+
Import `graphqlPlugin` and add an instance per API. The `path` is where the
|
|
167
|
+
docs mount, and `input` points at your schema:
|
|
168
|
+
|
|
169
|
+
```tsx title="zudoku.config.tsx"
|
|
170
|
+
import { graphqlPlugin } from "@zudoku/plugin-graphql";
|
|
171
|
+
|
|
172
|
+
const config = {
|
|
173
|
+
plugins: [
|
|
174
|
+
graphqlPlugin({
|
|
175
|
+
type: "file",
|
|
176
|
+
input: "./schema.graphql",
|
|
177
|
+
path: "/graphql/ecommerce",
|
|
178
|
+
options: {
|
|
179
|
+
title: "E-Commerce GraphQL API",
|
|
180
|
+
description: "Products, orders, and customers.",
|
|
181
|
+
playground: {
|
|
182
|
+
endpoint: "https://my-gateway.example.com/graphql",
|
|
183
|
+
},
|
|
184
|
+
},
|
|
185
|
+
}),
|
|
186
|
+
],
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
export default config;
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
Set `playground.endpoint` to the gateway route from the first half of this
|
|
193
|
+
guide so readers run real queries through your gateway. With a file-based
|
|
194
|
+
schema the plugin doesn't know where your API lives — leave the endpoint
|
|
195
|
+
unset and the reference pages still render, but the playground asks you to
|
|
196
|
+
configure `options.playground.endpoint` before it can run operations.
|
|
197
|
+
|
|
198
|
+
With `type: "url"`, set `input` to your GraphQL endpoint's URL instead: the
|
|
199
|
+
schema is fetched via introspection when the portal builds, and the
|
|
200
|
+
playground defaults to that same URL. You can register the plugin more than
|
|
201
|
+
once to document several schemas, each with its own `path`.
|
|
202
|
+
|
|
203
|
+
4. **Link it in the navigation**
|
|
204
|
+
|
|
205
|
+
Point a navigation link at the instance's `path`. Set `stack: true` so the
|
|
206
|
+
API's own pages render as a stacked sub-navigation instead of expanding
|
|
207
|
+
inline:
|
|
208
|
+
|
|
209
|
+
```tsx title="zudoku.config.tsx"
|
|
210
|
+
navigation: [
|
|
211
|
+
{
|
|
212
|
+
type: "category",
|
|
213
|
+
label: "Documentation",
|
|
214
|
+
items: [
|
|
215
|
+
{
|
|
216
|
+
type: "link",
|
|
217
|
+
label: "E-Commerce API",
|
|
218
|
+
to: "/graphql/ecommerce",
|
|
219
|
+
stack: true,
|
|
220
|
+
},
|
|
221
|
+
],
|
|
222
|
+
},
|
|
223
|
+
];
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
Prefer the API as its own top-level section instead? Drop the link at the top
|
|
227
|
+
level of `navigation` and leave off `stack`. See the
|
|
228
|
+
[navigation reference](../dev-portal/zudoku/configuration/navigation.mdx) for
|
|
229
|
+
all link, category, and stacking options.
|
|
230
|
+
|
|
231
|
+
</Stepper>
|
|
232
|
+
|
|
233
|
+
### Plugin options
|
|
234
|
+
|
|
235
|
+
All options live under the instance's `options` key:
|
|
236
|
+
|
|
237
|
+
| Option | Type | Description |
|
|
238
|
+
| --------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
239
|
+
| `title` | `string` | Display title for the API's overview page. |
|
|
240
|
+
| `description` | `string` | Short description shown on the overview page. |
|
|
241
|
+
| `showDeprecated` | `boolean` | Include deprecated fields and operations in the reference. |
|
|
242
|
+
| `playground.enabled` | `boolean` | Show the playground. Defaults to `true`. |
|
|
243
|
+
| `playground.endpoint` | `string` | URL the playground sends operations to. Defaults to `input` for `type: "url"`. File schemas have no default; the playground prompts for one until set. |
|
|
244
|
+
| `playground.headers` | `object` | Default headers the playground sends with each operation. |
|
|
245
|
+
|
|
246
|
+
## Verify it worked
|
|
247
|
+
|
|
248
|
+
Open the
|
|
249
|
+
[**Environments**](https://portal.zuplo.com/+/account/project/environments) tab
|
|
250
|
+
in the Zuplo Portal — your build is listed there along with the URLs for both
|
|
251
|
+
your gateway and your Dev Portal. Send a query through the gateway URL:
|
|
252
|
+
|
|
253
|
+
```bash
|
|
254
|
+
curl https://my-gateway.example.com/graphql \
|
|
255
|
+
-H "Content-Type: application/json" \
|
|
256
|
+
-d '{"query":"{ __typename }"}'
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
A working route returns a response from your upstream, such as
|
|
260
|
+
`{"data":{"__typename":"Query"}}`.
|
|
261
|
+
|
|
262
|
+
For the Dev Portal, open its URL from the **Environments** tab (or run
|
|
263
|
+
`npm run dev` in your `docs/` folder) and go to the plugin's `path` (for example
|
|
264
|
+
`/graphql/ecommerce`). The overview page lists the schema's types, and the
|
|
265
|
+
playground runs operations against your configured endpoint.
|
|
266
|
+
|
|
267
|
+
## Next steps
|
|
268
|
+
|
|
269
|
+
- [Secure your GraphQL API](./graphql-security.mdx) — complexity limits and
|
|
270
|
+
introspection policies
|
|
271
|
+
- [Testing GraphQL queries](./testing-graphql.mdx) — test the endpoint from the
|
|
272
|
+
Zuplo Portal or external tools
|
|
273
|
+
- [URL Rewrite handler](../handlers/url-rewrite.mdx) — full reference for the
|
|
274
|
+
handler GraphQL routes use
|
|
275
|
+
- [MCP Server GraphQL endpoints](../mcp-server/graphql.mdx) — expose GraphQL
|
|
276
|
+
queries as MCP tools for AI agents
|
|
@@ -206,12 +206,26 @@ jobs:
|
|
|
206
206
|
- name: Install dependencies
|
|
207
207
|
run: npm install
|
|
208
208
|
|
|
209
|
+
- name: Get deployment URL
|
|
210
|
+
id: get-url
|
|
211
|
+
uses: actions/github-script@v7
|
|
212
|
+
with:
|
|
213
|
+
script: |
|
|
214
|
+
const comments = await github.rest.issues.listComments({
|
|
215
|
+
issue_number: context.issue.number,
|
|
216
|
+
owner: context.repo.owner,
|
|
217
|
+
repo: context.repo.repo,
|
|
218
|
+
});
|
|
219
|
+
const match = comments.data
|
|
220
|
+
.map((c) => c.body.match(/Deployed to: (https:\/\/\S+)/))
|
|
221
|
+
.find(Boolean);
|
|
222
|
+
core.setOutput("url", match ? match[1] : "");
|
|
223
|
+
|
|
209
224
|
- name: Delete environment
|
|
225
|
+
if: steps.get-url.outputs.url != ''
|
|
210
226
|
run: |
|
|
211
|
-
BRANCH_NAME="${{ github.head_ref }}"
|
|
212
|
-
ENV_NAME="${BRANCH_NAME//\//-}"
|
|
213
227
|
npx zuplo delete \
|
|
214
|
-
--
|
|
228
|
+
--url "${{ steps.get-url.outputs.url }}" \
|
|
215
229
|
--api-key "$ZUPLO_API_KEY" \
|
|
216
230
|
--wait
|
|
217
231
|
```
|