{
  "source_file": "0930",
  "source_platform": "个人博客",
  "author": "Vicki Boykis",
  "title_listed": "How FastAPI path operations work",
  "keywords": "FastAPI,路由,Web",
  "domain": "机器学习",
  "content_type": "深度技术解析",
  "is_paid_flag": "否",
  "url": "https://vickiboykis.com/2025/01/14/how-fastapi-path-operations-work/",
  "publish_date": "2025-01",
  "corpus_stage": "机器学习",
  "notes": "FastAPI 路由机制",
  "id": "2f5eb949702e",
  "collected_at": "2026-10-02T15:23:02",
  "title": "How FastAPI path operations work | ✰Vicki Boykis✰",
  "body_markdown": "How FastAPI path operations work\nJan 14 2025\nIf you’re building a new Python web app these days, there’s a good chance you’re using FastAPI. There are a lot of features that make FastAPI easy to get started with. There are also a lot of nuances that take a while to understand. One feature I’ve been untangling is the way FastAPI manages calls to API routes\nvia decorated path parameters.\nThe new year is a perfect time to take a deeper dive.\nWhat happens in a web server\nWhen we build a web app, one of the critical components is\na web server\n, a program that listens for incoming requests from the network. It then translates those requests into methods that are called in the backend.\nTo better understand what’s going on under the covers, we can first implement a simple web server using the\nhttp.server\nmodule\nincluded in Python’s standard library\n.\nWe need to write a program that listens on a port and accepts HTTP requests. It accepts the request, parses the path route, and parses any data attached to the HTTP call. Or,\n“All I want is to cURL and parse a JSON object”\n.\nimport\njson\nfrom\nhttp.server\nimport\nBaseHTTPRequestHandler, HTTPServer\nfrom\nurllib.parse\nimport\nurlparse, parse_qs\nclass\nRequestHandler\n(BaseHTTPRequestHandler):\ndef\nparse_path\n(self, request_path: str)\n->\ndict:\n\"\"\"\nParse request path\n\"\"\"\nparsed\n=\nurlparse(request_path)\nparams_dict\n=\nparse_qs(parsed\n.\nquery)\nreturn\nparams_dict\ndef\nstore_urls\n(self, request_path: str)\n->\nNone\n:\n\"\"\"\nParse URLs and store them\n\"\"\"\nparams\n=\nself\n.\nparse_path(request_path)\nfor\nkey, val\nin\nparams\n.\nitems():\nself\n.\ndata_store\n.\nput_data(val[\n0\n])\ndef\nreturn_k_json\n(self, k:dict)\n->\nBinaryIO:\n\"\"\"\nReturn json response\n\"\"\"\nself\n.\nsend_response(\n200\n)\nself\n.\nsend_header(\n\"Content-type\"\n,\n\"application/json\"\n)\nself\n.\nend_headers()\n# Contains the output stream for writing a response back to the client.\n# BufferedIOBase that writes to a stream\n# See https://docs.python.org/3/library/io.html#io.BufferedIOBase.write\nself\n.\nwfile\n.\nwrite(json\n.\ndumps(k)\n.\nencode(\n'utf-8'\n))\ndef\nbad_request\n(self):\n\"\"\"\nHandle bad request\n\"\"\"\nself\n.\nsend_response(\n400\n)\nself\n.\nsend_header(\n\"Content-type\"\n,\n\"application/json\"\n)\nself\n.\nend_headers()\ndef\ndo_GET\n(self):\nrequest_path\n=\nself\n.\npath\nif\nself\n.\npath\n==\n\"/\"\n:\nself\n.\nreturn_k_json({\n\"ciao\"\n:\n\"mondo\"\n})\nif\nrequest_path\n.\nstartswith(\n\"/get\"\n):\nkey\n=\nself\n.\nparse_path(request_path)\nself\n.\nreturn_k_json({\n\"jars\"\n: key[\n\"key\"\n]})\nself\n.\nsend_response(\n200\n)\nelse\n:\nself\n.\nbad_request()\nself\n.\nend_headers()\ndef\ndo_POST\n(self):\nrequest_path\n=\nself\n.\npath\nif\nrequest_path\n.\nstartswith(\n\"/set\"\n):\nself\n.\nstore_urls(request_path)\nself\n.\nsend_response(\n200\n)\nelse\n:\nself\n.\nbad_request()\nif\n__name__\n==\n\"__main__\"\n:\nhost\n=\n\"localhost\"\nport\n=\n8000\nserver\n=\nHTTPServer((host, port), RequestHandler)\nprint(\n\"Server started http://\n%s\n:\n%s\n\"\n%\n(host, port))\nserver\n.\nserve_forever()\nWhat’s going on here?\nLet’s say that we produce Nulltella, an artisinal hazlenut spread for statisticians, and are looking to build a web app that keeps track of\nall of our Nulltella jars so we can later stand up a prediction service.\nWe would start by designing a super simple API: As users,\nwe want to test the server and get back a simple response\nwe’d like to add jars to our inventory, and\nto see the jars we added.\nWe translate these actions to GET and PUT requests so we can write HTTP calls for them. For simplicity’s sake, we won’t actually store them server-side but we will write them so we can can very simply see how to send data to our app:\nWe want to test the server:\n> python serve.py\n> curl -X POST http://localhost:8000/\n>\n{\n\"ciao\"\n:\n\"mondo\"\n}\nWe want to store items:\n> curl -X POST http://localhost:8000/set\n\\?\nkey\n\\=\n8\n200\nOK\nAnd get back the stored items:\n> curl -X GET http://localhost:8000/get\n\\?\nkey\n\\=\n8\n>\n{\n\"jars\"\n:\n[\n\"8\"\n]}\nOur server needs a way to parse the key pieces of information it receives:\nThey type of request.\ndo_GET\nand\ndo_POST\nhandle this implicitly\nin the\nHTTP implementation\n.\nThe parameters we pass to the path request so that we can do something with them\nA route to a method inside our application itself that processes the data\nIn our simple server, the heart of the routing happens at the method level. If we send a base path, we return\n{\"ciao\": \"mondo\"}\n. Otherwise, we return the amount of jars we’ve passed in via the request path by parsing the parameters in the path.\ndef\ndo_GET\n(self)\n->\nNone\n:\nrequest_path\n=\nself\n.\npath\nif\nself\n.\npath\n==\n\"/\"\n:\nself\n.\nreturn_k_json({\n\"ciao\"\n:\n\"mondo\"\n})\nif\nrequest_path\n.\nstartswith(\n\"/get\"\n):\nkey\n=\nself\n.\nparse_path(request_path)\n# action performed within the web app here\nself\n.\nreturn_k_json({\n\"jars\"\n: key[\n\"key\"\n]})\nself\n.\nsend_response(\n200\n)\nelse\n:\nself\n.\nbad_request()\nself\n.\nend_headers()\nWe can see how this can become complicated quickly. For example, what if we have multiple operations we perform during a\nGET\n: what if we get data from a database, or a cache, or we retrieve assets? We’ll have different methods that we process depending on how the path is parsed. What if we also have\nPUT/DELETE\nverbs? What if we need authentication? To write to a database? Static pages? Our code complexity relative to our starting point starts to grow, and we now need a framework.\nStarlette\nEarly Python web dev frameworks include juggernauts\nDjango\nand Flask. More recently, since Python’s async story has grown stronger, frameworks like\nStarlette\nhave come onto the scene to include async functionality out of the box.\nStarlette was built by the creator of Django Rest Framework and includes lightweight operations for the core functionality of HTTP calls and additional operations like web sockets, with the added bonus of being async by default.\nTo manage an HTTP call the same way we would with our simple server, we can do the following with Starlette:\nfrom\nstarlette.applications\nimport\nStarlette\nfrom\nstarlette.responses\nimport\nJSONResponse\nfrom\nstarlette.routing\nimport\nRoute\nasync\ndef\nhomepage\n(request):\nreturn\nJSONResponse({\n'ciao'\n:\n'mondo'\n})\napp\n=\nStarlette(debug\n=\nTrue\n, routes\n=\n[Route(\n'/'\n, homepage),])\nWe start an instance of a Starlette application, which has\nprocesses routes.\nEach route is linked\n, at the path level, to the actual method it calls. If Starlette sees that specific route, it calls the method, taking into account logic for parsing and reading HTTP request headers and bodies.\nWhat if we want to add a second method call based on a different route, getting our jar count again?\nfrom\nstarlette.applications\nimport\nStarlette\nfrom\nstarlette.responses\nimport\nJSONResponse\nfrom\nstarlette.routing\nimport\nRoute\nasync\ndef\nhomepage\n(request):\nreturn\nJSONResponse({\n'ciao'\n:\n'mondo'\n})\nasync\ndef\nget_jars\n(request):\nreturn\nJSONResponse({\n'jars'\n: [\n'8'\n]})\napp\n=\nStarlette(debug\n=\nTrue\n, routes\n=\n[\nRoute(\n'/'\n, homepage),\nRoute(\n'/get_jars'\n, get_jars)\n])\nWe see that we are also passing and processing params, and there is\nlogic that processes the path params\nbased on the method as\nthey come in from the request\n.\nFastAPI’s implementation\nFastAPI wraps Starlette - “as it is basically Starlette on steroids” per the docs - and\nincludes Pydantic type validation\nat the logical boundaries of the application.\nUnder the covers, when we instantiate a FastAPI application, it’s really “just” an instance of a Starlette application with properties that we override at the application level.\nfrom\nfastapi\nimport\nFastAPI\napp\n=\nFastAPI()\n@app.get\n(\n\"/\"\n)\nasync\ndef\nroot\n():\nreturn\n{\n\"ciao\"\n:\n\"mondo\"\n}\n@app.get\n(\n\"/jars/\n{id}\n\"\n)\nasync\ndef\nget_jars\n(id):\nreturn\n{\n\"message\"\n:\nf\n\"jars:\n{\nid\n}\n\"\n}\nIn development, FastAPI uses\nuvicorn\n, an\nASGI server\nto listen for incoming requests and handle them according to the routes defined in your application.\nUvicorn initializes the ASGI server, binds it to a\nsocket connections\non\nport\n8000\n, and starts listening for incoming connections. So, when we send a\nGET\nrequest to the main route hosted by default on port 8000, we expect to get back\nciao mondo\nas a response.\nLike our previous applications, FastAPI is still delegating path operations and methods to a router that processes them and parses parameters, but it wraps these in a\nPython decorator\n. This is easier to write, but adds a level of complexity at the layer of understanding how the path processing actually happens.\nWhen we perform a path operation in FastAPI, we’re performing the equivalent work of routing that we do with our simple method, but with a lot more rigor and nested definitions.\nWithin our simple server, we:\nStart the server\nListen on port\n8000\nfor incoming requests\nWhen we receive a request, we route it to the\ndo_GET\nmethod\nDepending on the path of the request, we route it to\n\"/\"\nWe return the results to the client via a\n200\nstatus\nIn FastAPI, we:\nStart the uvicorn web server (if in development mode, if production we have to choose gunicorn using the\ncompatible worker class\n)\nListen on port\n8000\nfor incoming requests\nWe instantiate an instance of the FastAPI application\nThis in turn instantiates an instance of Starlette\nWhen we receive a\nGET\nrequest, it’s routed to the application’s\nself.get\nmethod\nThis in turn calls\nself.router.get\nwith\nthe path operation\nThe router is an instance of\nrouting.APIRouter\nThe\n.get\nmethod on\nAPIRouter\ntakes the path\nand retunrs\nreturn self.api_route\n. This is the point where the decorater is actually called -\nwe can see the decorator in that method\ntakes a\nDecoratedCallable\nfunction as input and returns a decorated\nadd_api_route\n, which actually\nappends the route to the list of routes.\nThis is purely the set of steps that happens for correct routing - and we didn’t yet address how the path parameters in the path are processed.\nPath Parameter Routing\nPath parameter routing happens in Starlette, where\npath parameters are parsed out\nof the\nrequest\ninto a dictionary (just like we do in our simple web application), via the magic of\nJinja Templating.\nTL; DR\nWhen we write a route in FastAPI that accepts path parameters, we are creating a lengthy callstack that goes through several levels of logic in FastAPI using decorators as input into an application that routes requests and appends methods using decorators to a group of route methods; those requests are then passed onto Starlette which does the work of parsing the path variables, using Jinja templates, into dictionaries which the application can then work with and return data to you!\n#python\n#web dev\n#engineering\n#code\n#prod",
  "metadata": {
    "title": "How FastAPI path operations work | ✰Vicki Boykis✰",
    "description": "Down the rabbithole of web servers",
    "keywords": "python,web dev,engineering,code,prod,",
    "author": null,
    "og:type": "article",
    "og:url": "https://vickiboykis.com/2025/01/14/how-fastapi-path-operations-work/",
    "og:title": "How FastAPI path operations work",
    "og:description": "Down the rabbithole of web servers",
    "og:image": "https://vickiboykis.com/images/308394339-66ca00e6-1baf-4eb0-9d3d-112966beb797.png",
    "twitter:card": "summary",
    "twitter:url": "https://vickiboykis.com/2025/01/14/how-fastapi-path-operations-work/",
    "twitter:title": "How FastAPI path operations work",
    "twitter:description": "Down the rabbithole of web servers",
    "twitter:image": "https://vickiboykis.com/images/308394339-66ca00e6-1baf-4eb0-9d3d-112966beb797.png",
    "twitter:creator": "@vboykis",
    "twitter:site": "@vboykis",
    "article:published_time": "2025-01-14T00:00:00+00:00",
    "article:modified_time": "2025-01-14T00:00:00+00:00"
  },
  "paywall": {
    "listed_paid": false,
    "detected": false,
    "marker": "",
    "public_only": false,
    "policy": "付费/会员内容只保留页面公开可见部分，不绕过付费墙"
  },
  "crawl": {
    "success": true,
    "status_code": 200,
    "error": ""
  },
  "code_blocks": [
    {
      "file": "attachments/2f5eb949702e/code/body-000.txt",
      "chars": 2141,
      "language": "text"
    },
    {
      "file": "attachments/2f5eb949702e/code/body-001.txt",
      "chars": 76,
      "language": "text"
    },
    {
      "file": "attachments/2f5eb949702e/code/body-002.txt",
      "chars": 56,
      "language": "text"
    },
    {
      "file": "attachments/2f5eb949702e/code/body-003.txt",
      "chars": 67,
      "language": "text"
    },
    {
      "file": "attachments/2f5eb949702e/code/body-004.txt",
      "chars": 407,
      "language": "text"
    },
    {
      "file": "attachments/2f5eb949702e/code/body-005.txt",
      "chars": 260,
      "language": "text"
    },
    {
      "file": "attachments/2f5eb949702e/code/body-006.txt",
      "chars": 370,
      "language": "text"
    },
    {
      "file": "attachments/2f5eb949702e/code/body-007.txt",
      "chars": 193,
      "language": "text"
    }
  ],
  "images": [
    {
      "status": "ok",
      "url": "https://vickiboykis.com/images/308394339-66ca00e6-1baf-4eb0-9d3d-112966beb797.png",
      "bytes": 226191,
      "reused": false,
      "local_path": "attachments/2f5eb949702e/images/body-7d4de47fe73b44b5f446.png",
      "src": "https://vickiboykis.com/images/308394339-66ca00e6-1baf-4eb0-9d3d-112966beb797.png",
      "alt": "",
      "downloaded": true
    }
  ],
  "audio": [],
  "videos": [],
  "links": [
    {
      "href": "https://fastapi.tiangolo.com/tutorial/path-params/",
      "label": "via decorated path parameters.",
      "type": "a"
    },
    {
      "href": "https://newsletter.vickiboykis.com/archive/when-you-write-a-web-server-but-you-get-served/",
      "label": "a web server",
      "type": "a"
    },
    {
      "href": "https://github.com/python/cpython/blob/main/Lib/http/server.py",
      "label": "included in Python’s standard library",
      "type": "a"
    },
    {
      "href": "https://crawshaw.io/blog/programming-with-llms",
      "label": "“All I want is to cURL and parse a JSON object”",
      "type": "a"
    },
    {
      "href": "https://vickiboykis.com/2024/02/28/gguf-the-long-way-around/",
      "label": "all of our Nulltella jars so we can later stand up a prediction service.",
      "type": "a"
    },
    {
      "href": "https://stackoverflow.com/a/50944691",
      "label": "handle this implicitly",
      "type": "a"
    },
    {
      "href": "https://docs.python.org/3/library/http.server.html",
      "label": "HTTP implementation",
      "type": "a"
    },
    {
      "href": "https://www.david-dahan.com/blog/comparing-fastapi-and-django",
      "label": "Django",
      "type": "a"
    },
    {
      "href": "https://www.starlette.io/",
      "label": "Starlette",
      "type": "a"
    },
    {
      "href": "https://github.com/encode/starlette/blob/7c0d1e6d1a499e6eeb68d447321838be3927e83b/starlette/routing.py",
      "label": "processes routes.",
      "type": "a"
    },
    {
      "href": "https://www.starlette.io/routing/",
      "label": "Each route is linked",
      "type": "a"
    },
    {
      "href": "https://github.com/encode/starlette/blob/7c0d1e6d1a499e6eeb68d447321838be3927e83b/starlette/_utils.py",
      "label": "logic that processes the path params",
      "type": "a"
    },
    {
      "href": "https://github.com/encode/starlette/blob/7c0d1e6d1a499e6eeb68d447321838be3927e83b/docs/requests.md?plain=1",
      "label": "they come in from the request",
      "type": "a"
    },
    {
      "href": "https://fastapi.tiangolo.com/alternatives/",
      "label": "includes Pydantic type validation",
      "type": "a"
    },
    {
      "href": "https://www.uvicorn.org/",
      "label": "uvicorn",
      "type": "a"
    },
    {
      "href": "https://asgi.readthedocs.io/en/latest/",
      "label": "ASGI server",
      "type": "a"
    },
    {
      "href": "https://github.com/encode/uvicorn/blob/ae8253f10b9f73f10e92be52a0d9f70696b77c62/uvicorn/server.py",
      "label": "socket connections",
      "type": "a"
    },
    {
      "href": "https://github.com/encode/uvicorn/blob/ae8253f10b9f73f10e92be52a0d9f70696b77c62/uvicorn/main.py",
      "label": "port 8000",
      "type": "a"
    },
    {
      "href": "https://nedbatchelder.com/blog/202210/decorator_shortcuts.html",
      "label": "Python decorator",
      "type": "a"
    },
    {
      "href": "https://stackoverflow.com/a/71546833",
      "label": "compatible worker class",
      "type": "a"
    },
    {
      "href": "https://github.com/fastapi/fastapi/blob/144f09ea146b2cc026bf317f730aa0e0dbc3de24/fastapi/applications.py",
      "label": "self.get",
      "type": "a"
    },
    {
      "href": "https://github.com/fastapi/fastapi/blob/144f09ea146b2cc026bf317f730aa0e0dbc3de24/fastapi/routing.py",
      "label": "method on APIRouter takes the path",
      "type": "a"
    },
    {
      "href": "https://github.com/encode/starlette/blob/0109dce29b76c64e93c56c01fa5020860f935ed3/starlette/requests.py",
      "label": "path parameters are parsed out",
      "type": "a"
    },
    {
      "href": "https://github.com/encode/starlette/blob/0109dce29b76c64e93c56c01fa5020860f935ed3/starlette/templating.py",
      "label": "Jinja Templating.",
      "type": "a"
    },
    {
      "href": "https://vickiboykis.com/tags/python/",
      "label": "#python",
      "type": "a"
    },
    {
      "href": "https://vickiboykis.com/tags/web-dev/",
      "label": "#web dev",
      "type": "a"
    },
    {
      "href": "https://vickiboykis.com/tags/engineering/",
      "label": "#engineering",
      "type": "a"
    },
    {
      "href": "https://vickiboykis.com/tags/code/",
      "label": "#code",
      "type": "a"
    },
    {
      "href": "https://vickiboykis.com/tags/prod/",
      "label": "#prod",
      "type": "a"
    }
  ],
  "repair_version": 2,
  "repaired_at": "2026-10-14T10:53:15Z",
  "source_url": "https://vickiboykis.com/2025/01/14/how-fastapi-path-operations-work/",
  "papers": [],
  "actual_video_files": [],
  "body_chars": 10452,
  "expected_body_images": 1,
  "discovery_status": "ok",
  "delivery_status": "ok",
  "missing_reason": "",
  "raw_html": "attachments/2f5eb949702e/pages/source.html",
  "local_html": "attachments/2f5eb949702e/pages/article.html",
  "body_base_path": "attachments/2f5eb949702e/pages/",
  "body_markdown_path": "attachments/2f5eb949702e/pages/article.md"
}