Your first resource

A guided tour of the attributes you will set on a real resource.

Anatomy

class UserResource(BaseResource):
    model = User                            # required for CRUD
    authenticated = True                    # default — require session/api-key

    # field whitelists
    list_fields = ['id', 'email', 'name']   # what GET /users returns
    edit_fields = ['id', 'email', 'name']   # what GET /users/{id} returns
    create_fields = ['email', 'password']   # what POST /users accepts
    update_fields = ['name', 'email']       # what PATCH /users/{id} accepts

    # query helpers
    filter_fields = ['active', 'role']      # whitelist for ?active=...
    search_fields = ['email', 'name']       # ?search=foo runs ICONTAINS on these
    order_fields = ['id', 'email']          # ?order_by=email or -email

    # relations
    list_related_fields = {'account': ['id', 'name']}
    edit_related_fields = {'account': ['id', 'name', 'plan__name']}

    # ownership
    owner_field = 'owner_id'                # GET/LIST/PATCH/DELETE only on rows owned by user

    # cache
    cache = True
    cache_ttl = 60                          # seconds

Field whitelists are not optional

create_fields and update_fields are required for write methods. A POST or PATCH that touches any field outside the whitelist is rejected with 403. This is the simplest correct default for a public API.

⚠️

list_fields defaults to all fields when not set. edit_fields defaults to all model columns. Always set them explicitly when the model has sensitive columns (passwords, tokens, internal flags).

Authentication

By default authenticated = True. Requests must carry a valid session cookie, X-Api-Key header, or Authorization: Bearer (when BEARER_RESOLVER is configured). Public endpoints set authenticated = False.

Custom routes

Add methods that live alongside CRUD:

class UserResource(BaseResource):
    model = User
    routes = [
        {'path': r'/me$', 'func': 'me', 'allowed_methods': ['get']},
    ]

    async def me(self, request, match=None):
        return {'id': self.user['id'], 'email': self.user['email']}

This adds GET /users/me without a row id.

Hooks

Override any of these to adapt without subclassing dispatch:

0-mcp by Stamatios Stamou Jr — github.com/ssjunior/0-mcp