# HTTP verbs in REST services

Verb-specific handlers - one service responding differently to GET, POST, PUT, PATCH and DELETE.

REST APIs use HTTP verbs to express intent: GET retrieves data, POST creates resources, PUT replaces them, PATCH updates them partially and DELETE removes them. You can handle each verb with a dedicated method, so one service responds differently depending on how it is called.

Instead of checking the HTTP method inside `handle`, implement `handle_GET`, `handle_POST` and so on. Zato routes each request to the matching method and answers 405 Method Not Allowed when a client uses a verb the service does not implement. A service with verb-specific handlers never runs its `handle` method for REST requests.

## GET and DELETE in one service {#get-and-delete-in-one-service}

The service below reads a customer on GET and removes one on DELETE:

```python
# -*- coding: utf-8 -*-

# stdlib
from http import HTTPStatus

# Zato
from zato.server.service import Service

class CustomerResource(Service):
    """ Reads and deletes customers by their ID.
    """
    name = 'demo.rest.customer-resource'

    def handle_GET(self) -> 'None':
        customer_id = self.request.http.params['customer_id']
        customer = self.invoke('customer.get-by-id', customer_id=customer_id)
        self.response.payload = customer

    def handle_DELETE(self) -> 'None':
        customer_id = self.request.http.params['customer_id']
        self.invoke('customer.delete', customer_id=customer_id)
        self.response.status_code = HTTPStatus.NO_CONTENT
```

[Mount the service on a channel](https://zato.io/docs/dev/rest/channels.html) at `/api/customers/{customer_id}` and each verb reaches its handler:

```bash
# Get a customer
curl http://localhost:11223/api/customers/CUST-001

# Delete a customer
curl -X DELETE http://localhost:11223/api/customers/CUST-001

# POST is not implemented, so the channel returns 405 Method Not Allowed
curl -X POST http://localhost:11223/api/customers/CUST-001
```

## Full CRUD resource {#full-crud-resource}

A resource that supports every CRUD operation implements all five handlers:

```python
# -*- coding: utf-8 -*-

# stdlib
from http import HTTPStatus

# Zato
from zato.server.service import Service

class OrderResource(Service):
    """ A full CRUD resource for orders.
    """
    name = 'demo.rest.order-resource'

    def handle_GET(self) -> 'None':
        order_id = self.request.http.params['order_id']
        order = self.invoke('order.get-by-id', order_id=order_id)

        if not order:
            self.response.status_code = HTTPStatus.NOT_FOUND
            self.response.payload = {'error': 'Order not found'}
            return

        self.response.payload = order

    def handle_POST(self) -> 'None':
        data = self.request.payload
        new_order = self.invoke('order.create', data=data)

        new_order_id = new_order['id']

        self.response.status_code = HTTPStatus.CREATED
        self.response.headers['Location'] = f'/api/orders/{new_order_id}'
        self.response.payload = new_order

    def handle_PUT(self) -> 'None':
        order_id = self.request.http.params['order_id']
        data = self.request.payload
        updated = self.invoke('order.replace', order_id=order_id, data=data)
        self.response.payload = updated

    def handle_PATCH(self) -> 'None':
        order_id = self.request.http.params['order_id']
        changes = self.request.payload
        updated = self.invoke('order.update', order_id=order_id, changes=changes)
        self.response.payload = updated

    def handle_DELETE(self) -> 'None':
        order_id = self.request.http.params['order_id']
        self.invoke('order.delete', order_id=order_id)
        self.response.status_code = HTTPStatus.NO_CONTENT
```

Each verb maps to one operation:

```bash
# Create
curl -X POST http://localhost:11223/api/orders \
  -d '{"customer_id": "CUST-001", "items": [{"sku": "ABC", "qty": 2}]}'

# Read
curl http://localhost:11223/api/orders/ORD-123

# Update (partial)
curl -X PATCH http://localhost:11223/api/orders/ORD-123 \
  -d '{"status": "shipped"}'

# Replace (full)
curl -X PUT http://localhost:11223/api/orders/ORD-123 \
  -d '{"customer_id": "CUST-001", "items": [{"sku": "XYZ", "qty": 5}], "status": "pending"}'

# Delete
curl -X DELETE http://localhost:11223/api/orders/ORD-123
```

## Collection and item endpoints {#collection-and-item-endpoints}

A resource usually needs two endpoints - one for the collection, `/api/orders`, and one for individual items, `/api/orders/{id}`. Create two channels pointing to the same service and the service tells the requests apart by the path parameter that only the item channel defines:

```python
# -*- coding: utf-8 -*-

# stdlib
from http import HTTPStatus

# Zato
from zato.server.service import Service

class EmployeeAPI(Service):
    """ Serves both the employee collection and individual employees.
    """
    name = 'demo.rest.employee-api'

    def handle_GET(self) -> 'None':

        # The item channel has the employee_id path parameter, the collection channel does not
        if employee_id := self.request.http.params.get('employee_id'):
            employee = self.invoke('employee.get', employee_id=employee_id)
            self.response.payload = employee
        else:
            # List all employees, with the department read from the query string
            department = self.request.http.GET.get('department')
            employees = self.invoke('employee.list', department=department)
            self.response.payload = {'employees': employees}

    def handle_POST(self) -> 'None':

        # POST creates a new item in the collection
        data = self.request.payload
        new_employee = self.invoke('employee.create', data=data)
        self.response.status_code = HTTPStatus.CREATED
        self.response.payload = new_employee
```

## HEAD and OPTIONS {#head-and-options}

HEAD returns headers without a body and OPTIONS tells clients which methods the resource supports - both have their own handlers:

```python
# -*- coding: utf-8 -*-

# stdlib
from http import HTTPStatus

# Zato
from zato.server.service import Service

class DocumentResource(Service):
    """ Serves documents, with HEAD for existence checks.
    """
    name = 'demo.rest.document-resource'

    def handle_GET(self) -> 'None':
        doc_id = self.request.http.params['doc_id']
        document = self.invoke('document.get', doc_id=doc_id)
        self.response.payload = document

    def handle_HEAD(self) -> 'None':

        # Headers only, no body - callers use it to check that a document exists
        doc_id = self.request.http.params['doc_id']
        exists = self.invoke('document.exists', doc_id=doc_id)
        if not exists:
            self.response.status_code = HTTPStatus.NOT_FOUND

    def handle_OPTIONS(self) -> 'None':

        # The Allow header names the methods this resource supports
        self.response.headers['Allow'] = 'GET, HEAD, OPTIONS, DELETE'
```

For preflight `OPTIONS` requests that browsers send, see [CORS in REST channels](https://zato.io/docs/dev/rest/cors.html).

## See also {#see-also}

- [REST channels](https://zato.io/docs/dev/rest/channels.html) - Creating the channels that route requests to verb handlers
- [URL path matching](https://zato.io/docs/dev/rest/url-matching.html) - How methods and paths decide which channel a request reaches
- [CORS](https://zato.io/docs/dev/rest/cors.html) - Answering the OPTIONS preflights that browsers send
- [Error catalog](https://zato.io/docs/dev/rest/error-catalog.html) - The 405 and every other status code a channel returns

## Learn more {#learn-more}

- [REST integration overview](https://zato.io/integrations/rest/) - Start here to see how teams build REST integrations
- [In-depth API tutorial](https://zato.io/tutorials/main/01.html) - Master architectural patterns for production-ready integrations
- [Modern REST API tutorial](https://zato.io/tutorials/rest-api/python.html) - Design, build and secure REST APIs with Python
