diff --git a/content/posts/how-to-implement-pagination-sorting-and-filtering-with-fastapi/index.md b/content/posts/how-to-implement-pagination-sorting-and-filtering-with-fastapi/index.md index f1166e4..4991aa9 100644 --- a/content/posts/how-to-implement-pagination-sorting-and-filtering-with-fastapi/index.md +++ b/content/posts/how-to-implement-pagination-sorting-and-filtering-with-fastapi/index.md @@ -31,9 +31,13 @@ GET /articles?page=2&items_per_page=20 ```json { "items": [...], - "total": 143, - "page": 2, - "total_pages": 8 + "pagination": { + "total_count": 143, + "items_per_page": 20, + "page": 2, + "has_more": true, + "pages": 8 + } } ``` @@ -52,8 +56,12 @@ GET /articles?cursor=eyJpZCI6IjEyMyJ9&items_per_page=20 ```json { "items": [...], - "next_cursor": "eyJpZCI6IjE0MyJ9", - "has_next": true + "pagination": { + "next_cursor": "eyJjcmVhdGVkX2F0IjogIjIwMjYtMDMtMTBUMDg6MTQ6MDBaIn0=", + "prev_cursor": null, + "items_per_page": 20, + "has_more": true + } } ``` @@ -207,22 +215,19 @@ With the CRUD factory declared, routes become thin wrappers. Each route uses [Ar async def list_articles_offset( session: SessionDep, params: Annotated[ - dict[str, Any], - Depends(ArticleCrud.offset_params(default_page_size=20, max_page_size=100)), + dict, + Depends( + ArticleCrud.offset_paginate_params( + default_page_size=20, + max_page_size=100, + default_order_field=Article.created_at, + ) + ), ], - filter_by: Annotated[dict[str, list[str]], Depends(ArticleCrud.filter_params())], - order_by: Annotated[ - OrderByClause | None, - Depends(ArticleCrud.order_params(default_field=Article.created_at)), - ], - search: str | None = None, ) -> OffsetPaginatedResponse[ArticleRead]: return await ArticleCrud.offset_paginate( session=session, **params, - search=search, - filter_by=filter_by or None, - order_by=order_by, schema=ArticleRead, ) ``` @@ -231,7 +236,7 @@ async def list_articles_offset( **Example request:** ``` -GET /articles/offset?page=2&items_per_page=2&search=fastapi&filter_by[status]=published&order_by=created_at&order_dir=desc +GET /articles/offset?page=2&items_per_page=2&search=fastapi&status=published&order_by=created_at&order_dir=desc ``` **Example response:** @@ -259,13 +264,16 @@ GET /articles/offset?page=2&items_per_page=2&search=fastapi&filter_by[status]=pu "total_count": 47, "items_per_page": 2, "page": 2, - "has_more": true + "has_more": true, + "pages": 24 }, "pagination_type": "offset", "filter_attributes": { "status": ["draft", "published", "archived"], "category__name": ["Python", "DevOps", "Architecture"] - } + }, + "search_columns": ["title", "body", "category__name"], + "order_columns": ["title", "created_at"] } ``` @@ -276,22 +284,19 @@ GET /articles/offset?page=2&items_per_page=2&search=fastapi&filter_by[status]=pu async def list_articles_cursor( session: SessionDep, params: Annotated[ - dict[str, Any], - Depends(ArticleCrud.cursor_params(default_page_size=20, max_page_size=100)), + dict, + Depends( + ArticleCrud.cursor_paginate_params( + default_page_size=20, + max_page_size=100, + default_order_field=Article.created_at, + ) + ), ], - filter_by: Annotated[dict[str, list[str]], Depends(ArticleCrud.filter_params())], - order_by: Annotated[ - OrderByClause | None, - Depends(ArticleCrud.order_params(default_field=Article.created_at)), - ], - search: str | None = None, ) -> CursorPaginatedResponse[ArticleRead]: return await ArticleCrud.cursor_paginate( session=session, **params, - search=search, - filter_by=filter_by or None, - order_by=order_by, schema=ArticleRead, ) ``` @@ -300,7 +305,7 @@ async def list_articles_cursor( **Example request (first page):** ``` -GET /articles/cursor?items_per_page=2&search=fastapi&filter_by[status]=published +GET /articles/cursor?items_per_page=2&search=fastapi&status=published ``` **Example response:** @@ -334,7 +339,9 @@ GET /articles/cursor?items_per_page=2&search=fastapi&filter_by[status]=published "filter_attributes": { "status": ["draft", "published", "archived"], "category__name": ["Python", "DevOps", "Architecture"] - } + }, + "search_columns": ["title", "body", "category__name"], + "order_columns": ["title", "created_at"] } ``` @@ -352,22 +359,19 @@ You can also expose a single endpoint that supports both strategies via a `pagin async def list_articles( session: SessionDep, params: Annotated[ - dict[str, Any], - Depends(ArticleCrud.paginate_params(default_page_size=20, max_page_size=100)), + dict, + Depends( + ArticleCrud.paginate_params( + default_page_size=20, + max_page_size=100, + default_order_field=Article.created_at, + ) + ), ], - filter_by: Annotated[dict[str, list[str]], Depends(ArticleCrud.filter_params())], - order_by: Annotated[ - OrderByClause | None, - Depends(ArticleCrud.order_params(default_field=Article.created_at)), - ], - search: str | None = None, ) -> PaginatedResponse[ArticleRead]: return await ArticleCrud.paginate( session, **params, - search=search, - filter_by=filter_by or None, - order_by=order_by, schema=ArticleRead, ) ``` @@ -376,7 +380,7 @@ async def list_articles( The response shape adapts to the chosen strategy. With `pagination_type=offset` (default): ``` -GET /articles/?pagination_type=offset&page=1&items_per_page=2&filter_by[status]=published +GET /articles/?pagination_type=offset&page=1&items_per_page=2&status=published ``` ```json { @@ -386,18 +390,21 @@ GET /articles/?pagination_type=offset&page=1&items_per_page=2&filter_by[status]= "items_per_page": 2, "page": 1, "has_more": true + "pages": 24, }, "pagination_type": "offset", "filter_attributes": { "status": ["draft", "published", "archived"], "category__name": ["Python", "DevOps", "Architecture"] - } + }, + "search_columns": ["title", "body", "category__name"], + "order_columns": ["title", "created_at"] } ``` With `pagination_type=cursor`: ``` -GET /articles/?pagination_type=cursor&items_per_page=2&filter_by[status]=published +GET /articles/?pagination_type=cursor&items_per_page=2&status=published ``` ```json { @@ -412,7 +419,9 @@ GET /articles/?pagination_type=cursor&items_per_page=2&filter_by[status]=publish "filter_attributes": { "status": ["draft", "published", "archived"], "category__name": ["Python", "DevOps", "Architecture"] - } + }, + "search_columns": ["title", "body", "category__name"], + "order_columns": ["title", "created_at"] } ```