Controllers
Controllers are responsible for handling incoming requests and returning responses to the client. In Nestipy, they serve as the primary entry point for HTTP traffic, organizing routes and logic into manageable classes.
A controller's purpose is to receive specific requests for the application. The routing mechanism controls which controller receives which requests. Frequently, each controller has more than one route, and different routes can perform different actions.
Basic Controller
To create a basic controller, we use the @Controller() decorator. This decorator defines a metadata entry for the class, allowing Nestipy to associate it with a specific route prefix.
from nestipy.common import Controller, Get
@Controller("cats")
class CatsController:
@Get()
async def find_all(self) -> str:
return "This action returns all cats"Routing Logic
In the example above:
@Controller("cats"): This prefix ensures that all routes defined within this class are prefixed with/cats. This helps in grouping related routes together.@Get(): This HTTP request method decorator tells Nestipy to create a handler for a specific endpoint. Since no path is provided to@Get(), it maps toGET /cats.
The find_all method returns a string. By default, Nestipy will take this return value and send it as the response body.
Request and Response Objects
While Nestipy provides high-level decorators for extracting data, sometimes you need full access to the underlying Request and Response objects (from FastAPI or BlackSheep). You can achieve this using the Req() and Res() dependency injection tokens.
from typing import Annotated
from nestipy.ioc import Req, Res
from nestipy.common import Controller, Get, Response, Request
@Controller("cats")
class CatsController:
@Get()
async def find_all(
self,
req: Annotated[Request, Req()],
res: Annotated[Response, Res()],
) -> Response:
# Full control over the response
return await res.send("This action returns all cats with custom handling")When to use Res()
Use the res object when you need to set custom headers, status codes, or cookies directly. However, for most cases, returning a value or using specific decorators is cleaner and more testable.
Full Resource Example
Nestipy provides a clean way to extract various parts of the HTTP request, such as the body, query parameters, route parameters, and headers.
from dataclasses import dataclass
from typing import Annotated
from nestipy.common import Controller, Get, Put, Post, Delete
from nestipy.ioc import Body, Query, Param, Session, Header
@dataclass
class CreateCat:
name: str
@Controller("cats")
class CatsController:
@Post()
async def create(self, data: Annotated[CreateCat, Body()]):
# data is automatically validated against the CreateCat dataclass
return "This action adds a new cat"
@Get()
async def find_all(
self,
limit: Annotated[int, Query("limit")],
headers: Annotated[dict, Header()],
):
# Extract 'limit' from query string and all headers
return f"This action returns all cats (limit: {limit} items)"
@Get("/{cat_id}")
async def find_one(self, cat_id: Annotated[str, Param("cat_id")]):
# Path parameter 'cat_id' is extracted from the URL
return f"This action returns a #{cat_id} cat"Parameter Decorators
Body(): Extracts the request body. If a type is provided (likeCreateCat), Nestipy/FastAPI will validate the incoming JSON.Query("name"): Extracts a query parameter from the URL string.Param("name"): Extracts a named parameter from the route path.Header("name"): Extracts a specific header or the entire header dictionary.
Versioning
Nestipy supports API versioning at both the controller and method level. This is crucial for maintaining backwards compatibility as your API evolves.
from nestipy.common import Controller, Get, Version
@Controller("cats")
@Version("1")
class CatsController:
@Get()
async def list_v1(self):
return ["v1"]
@Version("2")
@Get()
async def list_v2(self):
return ["v2"]This configuration results in the following accessible routes:
GET /v1/catsGET /v2/cats
INFO
The default version prefix is v. You can customize this in your NestipyConfig by setting router_version_prefix.
Route Caching
Performance is key. Use the @Cache() decorator to set Cache-Control headers for your responses, allowing clients or intermediaries to cache the results.
from nestipy.common import Controller, Get, Cache
@Controller("cats")
class CatsController:
@Cache(max_age=60, public=True)
@Get("/cached")
async def cached(self):
return {"cached": True, "timestamp": "..."}Applying Pipes
Pipes are used for data transformation and data validation. They can be applied at different levels to ensure that the data reaching your handlers is in the correct format.
from typing import Annotated
from nestipy.common import Controller, Get, UsePipes
from nestipy.ioc import Query
from nestipy.common.pipes import ParseIntPipe
@Controller("cats")
class CatsController:
@Get()
@UsePipes(ParseIntPipe) # Apply to all params in this method
async def find_all(self, limit: Annotated[int, Query("limit")]):
return {"limit": limit}Pipe Scopes
- Method Level: Using
@UsePipes(), the pipe applies to all parameters of the method. - Parameter Level: Applying the pipe within
Annotated[..., Query(..., Pipe)]targets a specific parameter.
Next Up: Discover how to encapsulate logic and share it across your application in the Providers section.
