Repository navigation
Feature: Custom Param Decorators (@Body, @Param, @Query, @Headers, createParamDecorator) #115
Description
Overview
PyNest route handlers today rely entirely on FastAPI's implicit parameter binding — function signature type hints are parsed by FastAPI directly. This means there is no PyNest-native way to:
- Explicitly declare where a parameter comes from (
@Body,@Param,@Query) - Build reusable domain-specific decorators like
@CurrentUser()or@TenantId() - Transform or validate individual parameters before they reach the handler
This feature request proposes a full suite of built-in param decorators and a public createParamDecorator factory API.
Motivation
```python
Today — implicit and ambiguous:
@post('/users')
def create_user(self, user_data: CreateUserDto, token: str = Header(None)):
...
With this feature — explicit and composable:
@post('/users')
def create_user(self, @Body() user_data: CreateUserDto, @currentuser() user: User):
...
```
Explicit param decorators also enable:
- Cleaner OpenAPI docs (FastAPI knows exactly what goes where)
- Param-level pipes and transforms (prerequisite for Feature add docker file if db is sqlite (in the future i will add support in … #5)
- Domain decorators reusable across the entire app
Built-in Param Decorators
@Body(key?: str)
```python
@post('/users')
def create_user(self, @Body() body: CreateUserDto):
# body is the full request body, validated against CreateUserDto
...
@post('/upload')
def upload(self, @Body('file') file: UploadFile):
...
```
@Param(name?: str)
```python
@get('/:user_id/posts/:post_id')
def get_post(self, @PARAM('user_id') uid: int, @PARAM('post_id') pid: int):
...
@get('/:user_id')
def get_user(self, @PARAM() params: dict):
# params = {"user_id": "123"}
...
```
@Query(name?: str)
```python
@get('/search')
def search(self, @query('q') query: str, @query('limit') limit: int = 20):
...
```
@Headers(name?: str)
```python
@get('/me')
def get_me(self, @headers('authorization') auth: str):
...
@get('/debug')
def debug(self, @headers() all_headers: dict):
...
```
@Req() and @Res()
```python
@get('/raw')
def raw(self, @Req() request: Request, @res() response: Response):
...
```
@Ip() and @HostParam()
```python
@post('/audit')
def audit(self, @ip() ip: str):
...
```
createParamDecorator — Custom Param Decorators
The real power: a public factory for building reusable param decorators with arbitrary extraction logic.
```python
from nest.common.decorators import createParamDecorator, ExecutionContext
Define once:
CurrentUser = createParamDecorator(
lambda data, ctx: ctx.switch_to_http().get_request().state.user
)
TenantId = createParamDecorator(
lambda data, ctx: ctx.switch_to_http().get_request().headers.get("x-tenant-id")
)
Pagination = createParamDecorator(
lambda data, ctx: {
"page": int(ctx.switch_to_http().get_request().query_params.get("page", 1)),
"limit": int(ctx.switch_to_http().get_request().query_params.get("limit", 20)),
}
)
Use anywhere:
@get('/posts')
def list_posts(self, @currentuser() user: User, @Pagination() pagination: dict):
return self.service.get_posts(user.id, **pagination)
@delete('/:id')
def delete_item(self, @PARAM('id') item_id: int, @TenantID() tenant: str):
...
```
data argument — parametrized decorators
```python
UserProperty = createParamDecorator(
lambda data, ctx: getattr(ctx.switch_to_http().get_request().state.user, data, None)
)
@get('/profile')
def profile(self, @userproperty('email') email: str):
...
```
Integration with Pipes (Feature #5)
Param decorators should accept an optional pipe argument for per-parameter transformation:
```python
@get('/:id')
def get(self, @PARAM('id', ParseIntPipe) id: int):
...
@post('/')
def create(self, @Body(ValidationPipe) body: CreateDto):
...
```
Implementation Approach
Since Python does not have native parameter-level decorators, this should be implemented via:
- A
ParamMetadatadescriptor or a sentinel object returned by each decorator - The controller decorator (
@Controller) or a route processing wrapper that inspects function signatures for these sentinels - FastAPI
Depends()under the hood for actual extraction and injection
Acceptance Criteria
-
@Body(key?),@Param(name?),@Query(name?),@Headers(name?)built-in decorators -
@Req(),@Res(),@Ip(),@HostParam()built-in decorators -
createParamDecorator(factory)public API - Custom decorators support a
dataargument for parameterized use - Param decorators compose with pipes (
@Param('id', ParseIntPipe)) -
ExecutionContextwithswitch_to_http()available inside factory functions - Full backward compatibility — existing routes without param decorators still work
- Unit tests for all built-in decorators
- Documentation page with
createParamDecoratorexamples
Related
- NestJS docs: https://docs.nestjs.com/custom-decorators
- Feature: Pipes & ValidationPipe (prerequisite for pipe argument on param decorators)
Activity
ItayTheDar commentedon May 13, 2026
Resolved by #129, which was merged on 2026-05-12.
Implemented scope:
Body,Param,Query,Headers,Req,Res,Ip,HostParam,createParamDecorator, andExecutionContext.dataarguments and pipe-style transformations.docs/param_decorators.mdand wired it intomkdocs.yml.Important syntax note: Python does not support NestJS-style parameter decorators inside a function parameter list, so the implementation uses PyNest marker defaults instead, for example:
Validation from #129:
uv run --group test python -m pytest -q-> 162 passed, 8 warningsuv run --group docs mkdocs build --strict-> completed successfully, with only the existing MkDocs/Material notice and existing nav warning noted in the PRClosing this as completed.