Zuplo
Zuplo is a fully-managed API gateway that offers the easiest way to securely and safely share your API. In this guide we look at how you can combine Zuplo and Supabase to create a public API with rate-limiting, a self-serve developer portal, and API-key authentication. There is an accompanying video for this article.
In this example we're going to work with a simple table that allows people to read and write entries to a Supabase table that contains some reviews of skis. Because this is an API for developers, we have to assume that they may be calling it from another backend service and can't login as a user using the standard Supabase method. In this scenario, API keys are often a better choice - see Wait, you're not using API keys?.
We'll allow people, with a valid API key, to read data from the ski results table and to create new records. Hopefully it's obvious that there are many ways that you can extend this example to add more behavior like roles based access, with custom policies, custom handlers and more.
Setting up Supabase#
If you haven't already, create a new project in Supabase and create a table called ski-reviews with the following columns:
- id (int8)
- created_at (timestamptz)
- make (text)
- model (text)
- year (int8)
- rating (int2)
- author (text)
Manually enter a couple of rows of data, so that we have something to read from the DB.
The Get all
reviews route in Zuplo#
Login to Zuplo at portal.zuplo.com and create a new project in Zuplo - I went with supabase-ski-reviews
.
Select the File tab and choose Routes. Add your first route with the following settings:
- method:
GET
- path:
/reviews
- summary:
Get all reviews
- version:
v1
- CORS:
Anything goes
And in the request handler section, paste the READ ALL ROWS
URL of your Supabase backend (you can get to this in the API docs section of Supabase)
- URL Rewrite:
https://YOUR_SUPABASE_URL.supabase.co/rest/v1/ski-reviews?select=*
- Forward Search:
unchecked
In order to call the Supabase backend I need to add some authentication headers to the outgoing request.
Expand the Policies section of your route. Click Add policy on the Request pipeline.
We don't want to forward any headers that the client sends us to Supabase, so find the Clear Headers Policy and add that to your inbound pipeline. Note, that we will allow the content-type
header to flow through, so this should be your policy config.
_10{_10 "export": "ClearHeadersInboundPolicy",_10 "module": "$import(@zuplo/runtime)",_10 "options": {_10 "exclude": ["content-type"]_10 }_10}
Next, we need to add the credentials to the outgoing request. We'll need to get the JWT token from supabase - you'll find it in Settings > API as shown below:
Once you've got your service_role JWT, click Add Policy again on the Request pipeline and choose the Add/Set Headers Policy and configure it as follows:
_18{_18 "export": "SetHeadersInboundPolicy",_18 "module": "$import(@zuplo/runtime)",_18 "options": {_18 "headers": [_18 {_18 "name": "apikey",_18 "value": "$env(SUPABASE_API_KEY)",_18 "overwrite": true_18 },_18 {_18 "name": "authorization",_18 "value": "$env(SUPABASE_AUTHZ_HEADER)",_18 "overwrite": true_18 }_18 ]_18 }_18}
Save your changes.
Next, create two secret environment variables as follows:
- SUPABASE_API_KEY:
"YOUR_SUPABASE_SECRET_ROLE_JWT"
- SUPABASE_AUTHZ_HEADER:
"Bearer YOUR_SUPABASE_SECRET_ROLE_JWT"
Obviously, in both instances replace YOUR_SUPABASE_SECRET_ROLE_JWT
with your service_role JWT from Supabase.
You are now ready to invoke your API gateway and see data flow through from your Supabase backend!
Click on the open in browser button shown below and you should see the JSON, flowing from Supabase in your browser 👏.
Adding authentication#
At this point, that route is wide open to the world so we need to secure it. We'll do this using API keys. You can follow this guide Add API key Authentication. Be sure to drag the API Key authentication policy to the very top of your Request pipeline. Come back here when you're done.
Welcome back! You've now learned how to secure your API with API-Keys.
Adding a Create route#
Next we'll add a route that allows somebody to create a review. Add another route with the following settings
- method:
POST
- path:
/reviews
- summary:
Create a new review
- version:
v1
- CORS:
Anything goes
And the request handler as follows:
- URL Rewrite:
https://YOUR_SUPABASE_URL.supabase.co/rest/v1/ski-reviews
- Forward Search:
unchecked
Expand the policies section and add the same policies (note you can reuse policies by picking from the existing policies at the top of the library)
- api-key-auth-inbound
- clear-headers-inbound
- set-headers-inbound
Now your create route is secured and will automatically set the right headers before calling Supabase. That was easy.
You can test this out by using the API Test Console to invoke your new endpoint. Go to the API Test Console and create a new test called create-review.json
.
- Method:
POST
- Path:
/v1/reviews
- Headers:
content-type
:application/json
authorization
:Bearer YOUR_ZUPLO_API_KEY
- Body:
_10{_10 "make": "Rossignol",_10 "model": "Soul HD7",_10 "rating": 5,_10 "year": 2019_10}
If you invoke your API by clicking Test
you should see that you get a 201 Created - congratulations!
Add validation to your post#
To make your API more usable and more secure it is good practice to validate incoming requests. In this case we will add a JSON Schema document and use it to validate the incoming body to our POST.
Create a new schema document called new-review.json
.
This example fits the ski-reviews table we described above
_43{_43 "$id": "http://example.com/example.json",_43 "type": "object",_43 "default": {},_43 "title": "Root Schema",_43 "required": ["make", "model", "rating", "year"],_43 "additionalProperties": false,_43 "properties": {_43 "make": {_43 "type": "string",_43 "default": "",_43 "title": "The make Schema",_43 "examples": ["DPS"]_43 },_43 "model": {_43 "type": "string",_43 "default": "",_43 "title": "The model Schema",_43 "examples": ["Pagoda"]_43 },_43 "rating": {_43 "type": "integer",_43 "default": 0,_43 "title": "The rating Schema",_43 "examples": [5]_43 },_43 "year": {_43 "type": "integer",_43 "default": 0,_43 "title": "The year Schema",_43 "examples": [2018]_43 }_43 },_43 "examples": [_43 {_43 "make": "DPS",_43 "model": "Pagoda",_43 "rating": 5,_43 "year": 2018,_43 "author": "Josh"_43 }_43 ]_43}
Now add a new policy to request pipeline for your Create new review
route. Choose the JSON Body Validation policy and configure it to use your newly created JSON schema document:
_10{_10 "export": "ValidateJsonSchemaInbound",_10 "module": "$import(@zuplo/runtime)",_10 "options": {_10 "validator": "$import(./schemas/new-review.json)"_10 }_10}
This policy can be dragged to the first position in your pipeline.
Now to test this is working, go back to your API test console and change the body of your create-review.json
test to be invalid (add a new property for example). You should find that you get a 400 Bad Request
response.
Finally, lean back and marvel at your beautiful Developer Portal that took almost zero effort to get this far, wow! Hopefully you already found the link for this when adding API key support :)
Zuplo can also be used to handle Supabase JWT tokens for any API, learn more at API Authentication with Supabase JWT Tokens