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 # secondsField 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:
pre_process(request)— runs before body parsingbefore_cache(request)— runs before cache lookuphydrate(body)— mutate the parsed body before validationdehydrate(row)— mutate each row before responsealter_list(results)/alter_detail(result)— final shapingpost_process(response)— last chance before serialization
0-mcp by Stamatios Stamou Jr — github.com/ssjunior/0-mcp