Integrating with API gateways
Azure, AWS or Kong in front of Zato - one gateway channel that invokes your services.
Zato is itself an API management platform - channels, security, rate limiting, quota tiers and the OpenAPI console are built in, so no separate gateway is required in front of it.
If your environment already standardizes on an API gateway - Azure API Management, AWS API Gateway, Kong or similar - the gateway can handle security checks and rate limiting while all the business logic stays in Zato. The gateway invokes a single Zato REST channel, passing your services all the business data and metadata, and no individual service is exposed to the gateway directly.
This also lets you keep the Zato installation on your own premises, or in a secure cloud location with restricted public access, with a public API gateway in the cloud in front of it - how cloud services reach an on-premise installation is covered in on-premise systems and cloud services.
Your services work the same whether invoked directly or through the gateway - nothing in their code changes for this configuration.
Create the gateway channel
To create the channel, go to Connections > Channels > REST in the Dashboard, click Create a new channel and fill in the form:
- Name: Channel for Azure API Management
- Service: helpers.service-gateway
- Data format: JSON
- Security: a Basic Auth definition for the gateway to use
- Gateway services: the services the gateway may invoke, one name per line
- Click OK

When you select helpers.service-gateway, the URL path is set to /zato/gateway/{service} automatically - you can change it to any other. The Gateway services list is the allowlist - a request naming any other service is rejected. In the channel list, gateway channels display a GW badge.
Write services
Services behind a gateway are regular Python services, with the gateway's metadata available on top of the business data:
# -*- coding: utf-8 -*-
# stdlib
from dataclasses import dataclass
# Zato
from zato.server.service import Model, Service
@dataclass
class GetCustomerRequest(Model):
customer_id: int
@dataclass
class GetCustomerResponse(Model):
name: str
email: str
tier: str
class GetCustomer(Service):
""" Returns customer details for gateway and direct callers alike.
"""
name = 'customer.get'
input = GetCustomerRequest
output = GetCustomerResponse
def handle(self) -> 'None':
# The end user the gateway authenticated
username = self.channel.security.username
self.logger.info('API call from %s', username)
# The business data from the request
customer_id = self.request.input.customer_id
# A custom header the gateway forwards - absent for direct callers
tenant = self.request.http.headers.get('x-zato-tenant')
customer = self.invoke('customer.get-details', customer_id, tenant=tenant)
self.response.payload.name = customer.name
self.response.payload.email = customer.email
self.response.payload.tier = customer.tier
The service behaves identically when called directly or through the gateway:
self.request.inputcontains the parsed requestself.channel.security.usernamecontains the authenticated userself.response.payloadreturns the response
HTTP headers
All HTTP headers that the gateway forwards to Zato are available in self.request.http.headers, with names normalized to lowercase with dashes, e.g. x-zato-tenant:
def handle(self) -> 'None':
tenant = self.request.http.headers.get('x-zato-tenant')
request_id = self.request.http.headers.get('x-request-id')
correlation_id = self.request.http.headers.get('x-correlation-id')
You can iterate over all headers:
def handle(self) -> 'None':
for key, value in self.request.http.headers.items():
self.logger.info('Header %s = %s', key, value)
Pass security information
The API gateway authenticates the API clients, so it forwards the authenticated username to Zato in the x-zato-username header:
- In Azure API Management, use a
set-headerpolicy - In AWS API Gateway, use a mapping template
- In Kong, use the
request-transformerplugin
Each gateway has its own mechanism, with the same result - the username arrives in the x-zato-username header and your services read it from self.channel.security.username:
def handle(self) -> 'None':
username = self.channel.security.username
self.logger.info('Request from user: %s', username)
This is the username of the end user who invoked the gateway - separate from the Basic Auth credentials the gateway itself uses to authenticate with Zato on the channel.
Call from the gateway
The gateway invokes services with the target service name in the URL path:
POST /zato/gateway/customer.get HTTP/1.1
Host: zato-server:17010
Content-Type: application/json
x-zato-username: john.doe
x-zato-tenant: acme
{"customer_id": 123}
Response:
Alternatively, the service name goes in a query parameter:
Both forms behave the same - some gateways express the path form more naturally and others the query parameter form.
Call directly
The same service can still be invoked directly, through its own REST channel:
POST /api/customer/get HTTP/1.1
Host: zato-server:17010
Content-Type: application/json
Authorization: Basic am9obi5kb2U6c2VjcmV0
{"customer_id": 123}
The response is identical and the service code does not change.
Gateway channels in enmasse
For automated deployments, gateway channels can be defined in YAML and imported with enmasse, so the gateway configuration is version-controlled alongside your services:
channel_rest:
- name: Gateway
url_path: /zato/gateway/{service}
service: helpers.service-gateway
data_format: json
gateway_service_list:
- customer.get
- customer.update
- orders.create
- orders.list
See also
| Page | What it covers |
|---|---|
| REST channels | The channel type gateway channels are built on |
| Authentication | The Basic Auth definition the gateway authenticates with |
| Enmasse reference | Every channel_rest key, gateway_service_list included |