DEV Community

David Landup
David Landup

Posted on

Guide to Path Variables and Query Parameters in Mitsuki Controllers

How to Handle URL Parameters in Mitsuki Controllers

A guide to reading path variables and query parameters with automatic type conversion.

Mitsuki

Mitsuki, is a web development framework, focused on bringing enterprise strength without the enterprise pain to Python.

It's opinionated, lightweight and performant.

Introduction

REST APIs usually involve reading dynamic data directly from the URL.

This data generally comes in two forms:

  • Path variables: which identify a specific resource (e.g., /posts/some_id)
  • Query parameters: which are usually used for sorting or filtering collections (e.g., /posts?popular=true).

Mitsuki makes handling both of these trivial and type-safe, allowing you to define complex URL-based data retrieval with minimal code.

Setup

For the purposes of this guide, let's create a simple Post entity, a @CrudRepository to access its data, and a @Service to contain our business logic:

from dataclasses import dataclass
from typing import List, Optional
from mitsuki import Entity, Id, CrudRepository, Service

# 1. The Entity: Defines the Post data model
@Entity()
@dataclass
class Post:
    id: int = Id()
    title: str = ""
    published: bool = True

# 2. The Repository: The data access layer for Posts
@CrudRepository(entity=Post)
class PostRepository:
    # Gets standard CRUD methods automatically.
    async def find_by_published(self, published: bool) -> List[Post]: ...

    # For custom filtering with pagination, we use @Query
    @Query("""
        SELECT p FROM Post p
        WHERE p.title LIKE :title_pattern AND p.published = :published
        ORDER BY p.id DESC
    """)
    async def search_paginated(self, title_pattern: str, published: bool, limit: int, offset: int) -> List[Post]: ...

# 3. The Service: Contains business logic
@Service()
class PostService:
    def __init__(self, repo: PostRepository):
        # Mitsuki automatically injects the PostRepository here
        self.repo = repo

    async def get_by_id(self, post_id: int) -> Optional[Post]:
        return await self.repo.find_by_id(post_id)

    async def search(self, q: str, published: bool, page: int, size: int) -> List[Post]:
        offset = page * size
        return await self.repo.search_paginated(
            title_pattern=f"%{q}%",
            published=published,
            limit=size,
            offset=offset
        )
Enter fullscreen mode Exit fullscreen mode

Path Variables: Accessing Resource Identifiers

Path variables are used to capture parts of the URL path, most commonly for resource IDs. You define a path variable in your mapping decorator with curly braces {} and accept it as an argument in your controller method.

Let's create an endpoint to fetch a specific post by its ID:

from mitsuki import RestController, GetMapping
from typing import Optional
from ..services.post_service import PostService
from ..domain.post import Post

@RestController("/api/posts")
class PostController:
    def __init__(self, post_service: PostService):
        # The PostService is automatically injected by Mitsuki's DI container
        self.post_service = post_service

    @GetMapping("/{post_id}")
    async def get_post_by_id(self, post_id: int) -> Optional[Post]:
        print(f"Fetching post with ID: {post_id}")
        print(f"Type of post_id is: {type(post_id)}")

        post = await self.post_service.get_by_id(post_id)

        return post
Enter fullscreen mode Exit fullscreen mode

There are two key things happening here:

  1. Name Matching: Mitsuki automatically matches the {post_id} in the path to the post_id argument in the method.
  2. Automatic Type Conversion: By type-hinting post_id: int, you tell Mitsuki to convert the incoming path segment from a string to an integer. If a client requests /api/posts/abc, Mitsuki will automatically respond with a 400 Bad Request error because "abc" cannot be converted to an integer.

Query Parameters: Filtering and Sorting

Query parameters are the key-value pairs that appear after the ? in a URL. They are ideal for optional filters, search queries, or pagination controls.

To read query parameters, you use the @QueryParam decorator on your method arguments. Let's add a search endpoint to our PostController.

# Continuing inside the PostController class...
from mitsuki import QueryParam

@GetMapping("/search")
async def search_posts(
    self,
    q: str = QueryParam(required=True),
    published: bool = QueryParam(default=True),
    page: int = QueryParam(default=0),
    size: int = QueryParam(default=10)
) -> List[Post]:
    # Example Request: /api/posts/search?q=mitsuki&published=true&page=0&size=20
    #
    # q: "mitsuki" (required string)
    # published: True (boolean, defaults to True)
    # page: 0 (integer, defaults to 0)
    # size: 20 (integer, defaults to 10)

    posts = await self.post_service.search(q, published, page, size)
    return posts
Enter fullscreen mode Exit fullscreen mode

Let's break down how @QueryParam works:

  • Required Parameter: q: str = QueryParam(required=True) explicitly marks the q parameter as mandatory. If a client makes a request without it, Mitsuki will automatically return a 400 Bad Request error with a descriptive message.
  • Optional Parameter with Default: published: bool = QueryParam(default=True) is optional. If the published parameter is missing from the URL, its value will be True. The type hint ensures that values like "true", "1", or "on" are correctly converted to the boolean True.
  • Optional Parameter defaulting to None: If you were to define a parameter as size: Optional[int] = QueryParam(default=None), it would default to None if not provided. A plain QueryParam() also creates an optional parameter that defaults to None.

Next Steps

You've seen how Mitsuki's declarative approach makes it easy to read and validate data from a URL. This type-safe mechanism helps prevent common bugs and cleans up your controller logic.

  • Explore the docs: For a full list of available decorators and validation options, see the official Controllers & Request Handling documentation.
  • Handle Request Bodies: Now that you can read data from a URL, learn how to handle data sent in the request body by reading our guide to @RequestBody.

Happy coding! ❀

Top comments (0)