Routing and handlers

MicroPie maps incoming HTTP requests to methods on your App subclass. This section explains how the mapping works and how your handlers receive input from the URL path, query strings, form data and sessions.

URL to function mapping

When an HTTP request arrives, MicroPie extracts the path portion of the URL and uses the first path segment to determine which method should handle the request. For example, a GET request to /greet calls greet on your App subclass. A request to the root URL / calls index. Only methods that do not start with an underscore are exposed as route handlers. Prefacing a method name with an underscore makes it private and hides it from external requests.

Handlers may be synchronous or asynchronous functions. They return either a string, bytes, a JSON‑serialisable object, a tuple of (status_code, body) or (status_code, body, headers), or an iterator/generator for streaming responses. See Streaming responses for details on streaming.

Parameters and arguments

MicroPie automatically populates handler arguments from several sources in the following order:

  1. Path parameters – Additional path segments after the first map to positional parameters. For example:

    class AppExample(App):
        async def greet(self, name):
            return f"Hello, {name}!"
    

    Visiting /greet/Alice passes "Alice" as the name argument. You can also use *args to capture an arbitrary number of path segments.

  2. Query parameters – Named parameters in the query string fill keyword arguments when path parameters are exhausted. For example, /greet?name=Alice also passes "Alice" to the name parameter.

  3. Body/form data – For POST, PUT and PATCH requests, form fields populate handler arguments. JSON bodies are decoded into a dictionary of key/value pairs.

  4. Files – If the request is multipart/form‑data, uploaded files appear in files. You can declare a file argument in your handler signature to receive a file object.

  5. Session data – Values stored in session fill remaining parameters. See Managing sessions for details.

  6. Default values – If no other source provides a value, default values in your function signature are used.

If MicroPie cannot determine a value for a required parameter, it returns a 400 Bad Request.

Choosing an input access style

MicroPie gives you three levels of request input access. Prefer the simplest level that fits the handler.

  1. Handler arguments are the most concise option. Use them when an endpoint needs a small number of named values and the normal binding order is acceptable.

    class MyApp(App):
        async def search(self, q="", page="1"):
            return {"q": q, "page": int(page)}
    

    This works for /search?q=micropie&page=2 without touching self.request directly.

  2. Helper methods are best when the handler needs explicit control over where a value comes from, a default value, or the full JSON payload.

    class MyApp(App):
        async def search(self):
            q = self.request.query("q", "")
            page = int(self.request.query("page", "1"))
            return {"q": q, "page": page}
    
        async def submit(self):
            username = self.request.form("username", "Anonymous")
            return {"submitted_by": username}
    
        async def api_submit(self):
            payload = self.request.json()
            username = self.request.json("username", "Anonymous")
            return {"submitted_by": username, "raw": payload}
    

    The helper methods are:

    • self.request.query(name, default=None): first query-string value for name.

    • self.request.form(name, default=None): first parsed body value for name from form data, or from a top-level JSON object mirrored into body parameters.

    • self.request.json(name=None, default=None): full parsed JSON payload when called with no name, or one top-level JSON object value when name is provided.

  3. Raw attributes are useful when you need the lower-level parsed structures, such as repeated query parameters, all submitted form values, uploaded files, or middleware decisions.

    • self.request.path_params: list of positional path segments.

    • self.request.query_params: dict[str, list[str]] parsed from the query string.

    • self.request.body_params: dict[str, list[str]] parsed from form-urlencoded payloads, multipart text fields, or mirrored from top-level JSON objects.

    • self.request.session: session dictionary.

    • self.request.files: uploaded multipart files.

    • self.request.headers: lower-case request header mapping.

    For example, use query_params when repeated values matter:

    class MyApp(App):
        async def filter(self):
            tags = self.request.query_params.get("tag", [])
            return {"tags": tags}
    

Do not await request helpers

The request helper methods are synchronous accessors over data MicroPie has already parsed for the current request. Do not use await with query(), form() or json():

class MyApp(App):
    async def submit(self):
        data = self.request.json()
        username = self.request.form("username", "")
        q = self.request.query("q", "")
        return {"data": data, "username": username, "q": q}

For JSON and URL-encoded form requests, the body has been read before your handler runs. Multipart file uploads are the main exception: uploaded file content is exposed as an asyncio.Queue in self.request.files and should be read asynchronously.

Examples

The following examples illustrate common patterns:

class MyApp(App):
    async def greet(self, name="Guest"):
        """Return a greeting for a named visitor.

        If the ``name`` argument is not provided via the path or
        query parameters, ``"Guest"`` is used as a default.
        """
        return f"Hello, {name}!"

    async def add(self, x, y):
        """Add two numbers provided via path segments.

        Example: ``/add/2/3`` returns ``5``.
        """
        return str(int(x) + int(y))

    async def submit(self, username="Anonymous"):
        """Handle a form submission.

        This handler accepts POST requests and uses the
        ``username`` field from the request body.
        """
        return f"Form submitted by: {username}"

See Request objects for the attributes available on the current request and App class for details about the App class.

HTTP methods and responses

Handlers run for any HTTP method unless you explicitly check self.request.method inside the handler. It is your responsibility to dispatch based on the method if your endpoint should behave differently for GET and POST. Handlers may return:

  • A string or bytes – sent as the body of the response with a 200 OK status.

  • A JSON‑serialisable object – automatically encoded into JSON and returned with a Content‑Type of application/json.

  • A tuple (status_code, body) – sets the HTTP status and body.

  • A tuple (status_code, body, headers) – sets status, body and additional headers. Headers should be a list of (name, value) pairs.

  • A generator or async generator – streams chunks to the client. Use this for large responses or server‑sent events.

Advanced routing

You can implement explicit routing, path prefixing or complex dispatching by writing a custom HttpMiddleware. See the Writing middleware guide and the examples in the examples/explicit_routing directory of the source distribution.