REST channels

Expose any service as a REST endpoint that clients call over HTTP.

A REST channel connects an incoming HTTP request to one of your services. When a client calls the channel's URL path, Zato invokes the service the channel names, passes the request's parameters to it and returns the service's response.

Channels can also be created automatically for deployed services, based on patterns of service names.

Create a channel

To create a channel, go to Connections > Channels > REST in the Dashboard, click Create a new channel and fill in the form:

  1. Name: Customer Orders
  2. URL path: the path clients call, e.g. /api/orders
  3. Service: the service that handles requests to this path
  4. Data format: JSON
  5. Click OK

The channel accepts requests the moment you click OK - configuration changes propagate to all servers automatically, with no restarts.

You can also pick a Security definition in the same form - a channel without one accepts unauthenticated requests. Basic Auth, API keys and bearer tokens are covered in authentication.

Use data models

Define the request and response as Python dataclasses and Zato parses incoming JSON into the request model and serializes the response model back to JSON. The service below creates an order from three input fields:

# -*- coding: utf-8 -*-

# stdlib
from dataclasses import dataclass

# Zato
from zato.server.service import Model, Service

@dataclass(init=False)
class CreateOrderRequest(Model):
    customer_id: str
    product_id: str
    quantity: int

@dataclass(init=False)
class CreateOrderResponse(Model):
    order_id: str
    status: str

class CreateOrder(Service):
    """ Creates an order for a customer and product.
    """
    name = 'demo.rest.create-order'

    input = CreateOrderRequest
    output = CreateOrderResponse

    def handle(self) -> 'None':

        # The parsed request, already a CreateOrderRequest instance ..
        request = self.request.input

        # .. another service creates the order ..
        order_id = self.invoke('orders.create',
            customer_id=request.customer_id,
            product_id=request.product_id,
            quantity=request.quantity
        )

        # .. and the response model serializes back to JSON.
        response = CreateOrderResponse()
        response.order_id = order_id
        response.status = 'created'

        self.response.payload = response
curl -X POST http://localhost:11223/api/orders \
  -d '{"customer_id": "CUST-001", "product_id": "PROD-123", "quantity": 2}'

Models give your IDE the field types to check before runtime, and the same definitions drive OpenAPI generation for your APIs.

Fields that the caller may omit are declared with optional from zato.common.typing_:

from zato.common.typing_ import optional

@dataclass(init=False)
class CreateOrderRequest(Model):
    customer_id: str
    product_id: str
    quantity: int
    notes: optional[str] = None

URL path parameters

Use curly braces to capture parts of the URL as parameters. For a URL path like /api/customers/{customer_id}/orders/{order_id}, Zato extracts both values and makes them available to your service through self.request.http.params:

# -*- coding: utf-8 -*-

# Zato
from zato.server.service import Service

class GetCustomerOrder(Service):
    name = 'demo.rest.get-customer-order'

    def handle(self) -> 'None':
        customer_id = self.request.http.params['customer_id']
        order_id = self.request.http.params['order_id']

        order = self.invoke('orders.get', customer_id=customer_id, order_id=order_id)
        self.response.payload = order
curl http://localhost:11223/api/customers/CUST-001/orders/ORD-123

params holds URL path parameters only - the query string has its own attribute, shown below.

Query string parameters

Query string parameters are available through self.request.http.GET:

# -*- coding: utf-8 -*-

# Zato
from zato.server.service import Service

# Defaults for query string parameters that the caller may omit
_default_status = 'all'
_default_limit = 100

class SearchOrders(Service):
    name = 'demo.rest.search-orders'

    def handle(self) -> 'None':
        status = self.request.http.GET.get('status', _default_status)

        # Query string values are always text
        if limit := self.request.http.GET.get('limit'):
            limit = int(limit)
        else:
            limit = _default_limit

        orders = self.invoke('orders.search', status=status, limit=limit)
        self.response.payload = orders
curl "http://localhost:11223/api/orders?status=pending&limit=10"

Other request details

The same self.request.http object holds everything else about the incoming request:

# The HTTP method, e.g. 'GET'
method = self.request.http.method

# The URL path as received, e.g. '/api/orders'
path = self.request.http.path

# All HTTP headers, with lower-case names, e.g. headers['x-api-key']
headers = self.request.http.headers

# The caller's User-Agent
user_agent = self.request.http.user_agent

JSON request body

For POST and PUT requests with JSON bodies, Zato parses the JSON automatically. Access the data through self.request.input:

# -*- coding: utf-8 -*-

# Zato
from zato.server.service import Service

class CreateCustomer(Service):
    name = 'demo.rest.create-customer'

    def handle(self) -> 'None':
        name = self.request.input['name']
        email = self.request.input['email']

        customer = self.invoke('customers.create', name=name, email=email)
        self.response.payload = customer
curl -X POST http://localhost:11223/api/customers \
  -d '{"name": "Alice Smith", "email": "alice@example.com"}'

Restrict HTTP methods

By default, a channel accepts every method from the server's [http] methods_allowed list, whose shipped default is GET, POST, DELETE, PUT, PATCH, HEAD and OPTIONS - a request with any other method receives 405 before routing. To restrict a channel further, enter one specific method in its configuration. The details are under URL path matching.

For more control, implement verb-specific handlers in your service - one service then handles GET, POST, PUT and DELETE differently on the same URL.

Data format

Set the channel's data format to match what clients send:

  • JSON - Zato parses incoming JSON and serializes responses automatically. This is the typical choice for REST APIs. See request and response handling for details and the message building examples for how responses are built through plain dot access.

  • Form data - for HTML form submissions or webhooks sending form-encoded data. Access fields through self.request.http.POST. For file uploads, see file handling.

What a channel controls

Each channel carries its own settings, independent of every other channel:

  • Security: one security definition or a security group with many credentials
  • Rate limiting: per-channel rate limiting rules
  • CORS: preflights from localhost work out of the box and the service itself serves other origins - see CORS in REST channels
  • Audit log: each user-defined channel records the requests it receives and the responses it sends in its own audit log - internal channels, such as the built-in publish/subscribe ones, are skipped
  • Response caching: a channel can serve repeated requests straight from its cache, without invoking the service at all - the settings sit behind the channel's own caching link in its Dashboard row, see response caching
  • API versioning: each major version of an API becomes its own channel and the old one carries a deprecation notice with a sunset date - see API versioning

See also

PageWhat it covers
URL path matchingHow a request finds its channel - paths, methods and priorities
AuthenticationBasic Auth, API keys and bearer tokens for channels
HTTP verbsOne service responding differently to GET, POST, PUT, PATCH and DELETE
Enmasse referenceEvery channel_rest key for YAML-based deployments

Learn more