[{"data":1,"prerenderedAt":1783},["ShallowReactive",2],{"page-\u002Fgetting-started-with-python-apis-for-builders\u002Fsetting-up-fastapi\u002Ffastapi-vs-django-rest-framework\u002F":3,"faq-schema-\u002Fgetting-started-with-python-apis-for-builders\u002Fsetting-up-fastapi\u002Ffastapi-vs-django-rest-framework\u002F":1762},{"id":4,"title":5,"body":6,"description":1751,"extension":1752,"meta":1753,"navigation":284,"path":1758,"seo":1759,"stem":1760,"__hash__":1761},"content\u002Fgetting-started-with-python-apis-for-builders\u002Fsetting-up-fastapi\u002Ffastapi-vs-django-rest-framework\u002Findex.md","FastAPI vs Django REST Framework: Which API Stack Should You Build On?",{"type":7,"value":8,"toc":1739},"minimark",[9,13,28,31,36,39,57,60,197,199,203,210,534,541,728,739,741,745,748,854,862,874,876,880,883,900,1061,1068,1323,1340,1342,1346,1349,1364,1382,1449,1456,1458,1462,1468,1484,1489,1503,1511,1513,1517,1520,1624,1641,1643,1647,1650,1654,1660,1666,1672,1678,1688,1692,1697,1716,1721,1735],[10,11,5],"h1",{"id":12},"fastapi-vs-django-rest-framework-which-api-stack-should-you-build-on",[14,15,16,17,22,23,27],"p",{},"Choosing between FastAPI and Django REST Framework is a choice between two philosophies: a lean async core you assemble yourself, or a batteries-included platform that hands you an ORM, admin, and auth on day one. Both ship real commercial APIs today, so the interesting question is not which is \"better\" but which one costs you less per million requests and gets your second product feature out this month. Part of the ",[18,19,21],"a",{"href":20},"\u002Fgetting-started-with-python-apis-for-builders\u002Fsetting-up-fastapi\u002F","Setting Up FastAPI"," guide, and a natural next step after comparing ",[18,24,26],{"href":25},"\u002Fgetting-started-with-python-apis-for-builders\u002Fsetting-up-fastapi\u002Ffastapi-vs-flask-for-api-development\u002F","FastAPI against Flask",".",[29,30],"hr",{},[32,33,35],"h2",{"id":34},"the-core-trade-off-lean-async-vs-batteries-included","The Core Trade-off: Lean Async vs Batteries Included",[14,37,38],{},"Django REST Framework (DRF) sits on top of Django, so when you pick it you inherit the whole Django stack: a mature ORM, database migrations, a generated admin site, a session and permissions system, and a vast plugin ecosystem. That is enormous leverage if your product is a data-heavy backend with relational models and human operators who need an admin panel. The admin alone can save you a fortnight of building internal CRUD screens you will never charge for.",[14,40,41,42,46,47,51,52,56],{},"FastAPI takes the opposite stance. It is an async-first web layer built on Starlette and ",[18,43,45],{"href":44},"\u002Fgetting-started-with-python-apis-for-builders\u002Fparsing-json-responses\u002Fvalidating-json-with-pydantic-v2\u002F","Pydantic",", with no opinion about your database, your auth, or your admin. You bring your own ORM — SQLAlchemy, SQLModel, Tortoise — and wire in only what you need. The result is a smaller surface area, native ",[48,49,50],"code",{},"async\u002Fawait",", and request validation baked into the type signatures of your handlers. It also produces an OpenAPI document you can hand to customers on day one, which matters more than people expect when you are ",[18,53,55],{"href":54},"\u002Fgetting-started-with-python-apis-for-builders\u002Fdocumenting-apis-with-openapi\u002F","documenting a public API"," that strangers must integrate against without talking to you.",[14,58,59],{},"Neither is more advanced than the other — they optimize for different things. DRF optimizes for breadth of built-in features. FastAPI optimizes for a tight async request path, typed contracts, and a dependency tree small enough that your container image stays under 200 MB. The decision compounds: it sets your auth story, your background job story, and how many boxes you rent at peak.",[61,62,70,71,70,75,70,79,70,86,70,94,70,102,70,108,70,118,70,123,70,126,70,130,70,133,70,137,70,140,70,144,70,148,70,152,70,156,70,159,70,163,70,166,70,170,70,175,70,180,70,183,70,187,70,190,70,194],"svg",{"viewBox":63,"role":64,"ariaLabelledBy":65,"xmlns":68,"style":69},"0 0 720 300","img",[66,67],"fdrf-stack-t","fdrf-stack-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;height:auto;margin:1.5rem 0;font-family:var(--font-sans);","\n  ",[72,73,74],"title",{"id":66},"FastAPI versus Django REST Framework feature stacks",[76,77,78],"desc",{"id":67},"DRF ships an ORM, admin, and auth out of the box on a synchronous core, while FastAPI provides a lean async core with Pydantic and lets you choose the rest.",[80,81],"rect",{"x":82,"y":82,"width":83,"height":84,"fill":85},"0","720","300","var(--c-surface)",[80,87],{"x":88,"y":88,"width":89,"height":90,"rx":91,"fill":85,"stroke":92,"style":93},"20","320","260","12","var(--c-blue)","stroke-width:2;",[95,96,101],"text",{"x":97,"y":98,"fill":99,"style":100},"180","48","var(--c-text)","text-anchor:middle;font-size:15;font-family:var(--font-sans);","Django REST Framework",[95,103,107],{"x":97,"y":104,"fill":105,"style":106},"68","var(--c-text-muted)","text-anchor:middle;font-size:11;font-family:var(--font-sans);","batteries included (WSGI sync)",[80,109],{"x":110,"y":111,"width":112,"height":113,"rx":114,"fill":115,"stroke":116,"style":117},"44","84","272","34","7","var(--c-surface-alt)","var(--c-border)","stroke-width:1.5;",[95,119,122],{"x":97,"y":120,"fill":99,"style":121},"106","text-anchor:middle;font-size:12;font-family:var(--font-sans);","ORM + migrations",[80,124],{"x":110,"y":125,"width":112,"height":113,"rx":114,"fill":115,"stroke":116,"style":117},"124",[95,127,129],{"x":97,"y":128,"fill":99,"style":121},"146","Admin site",[80,131],{"x":110,"y":132,"width":112,"height":113,"rx":114,"fill":115,"stroke":116,"style":117},"164",[95,134,136],{"x":97,"y":135,"fill":99,"style":121},"186","Auth + permissions",[80,138],{"x":110,"y":139,"width":112,"height":113,"rx":114,"fill":115,"stroke":116,"style":117},"204",[95,141,143],{"x":97,"y":142,"fill":99,"style":121},"226","Serializers + browsable API",[95,145,147],{"x":97,"y":146,"fill":105,"style":106},"262","thread-per-request",[80,149],{"x":150,"y":88,"width":89,"height":90,"rx":91,"fill":85,"stroke":151,"style":93},"380","var(--c-teal)",[95,153,155],{"x":154,"y":98,"fill":99,"style":100},"540","FastAPI",[95,157,158],{"x":154,"y":104,"fill":105,"style":106},"lean core (ASGI async)",[80,160],{"x":161,"y":111,"width":112,"height":162,"rx":114,"fill":115,"stroke":151,"style":117},"404","50",[95,164,165],{"x":154,"y":120,"fill":99,"style":121},"Async router",[95,167,169],{"x":154,"y":168,"fill":105,"style":121},"123","+ Pydantic validation",[80,171],{"x":161,"y":128,"width":172,"height":173,"rx":114,"fill":85,"stroke":116,"style":174},"130","40","stroke-width:1.5;stroke-dasharray:5 4;",[95,176,179],{"x":177,"y":178,"fill":105,"style":106},"469","170","ORM (BYO)",[80,181],{"x":182,"y":128,"width":172,"height":173,"rx":114,"fill":85,"stroke":116,"style":174},"546",[95,184,186],{"x":185,"y":178,"fill":105,"style":106},"611","Auth (BYO)",[80,188],{"x":161,"y":189,"width":112,"height":173,"rx":114,"fill":85,"stroke":116,"style":174},"198",[95,191,193],{"x":154,"y":192,"fill":105,"style":106},"222","Admin \u002F extras (optional)",[95,195,196],{"x":154,"y":146,"fill":105,"style":106},"single-thread event loop",[29,198],{},[32,200,202],{"id":201},"side-by-side-the-same-endpoint-in-each","Side by Side: The Same Endpoint in Each",[14,204,205,206,209],{},"Look at how each framework expresses a single typed ",[48,207,208],{},"POST \u002Fitems"," endpoint. The difference in ceremony and execution model is visible in a few lines.",[211,212,217],"pre",{"className":213,"code":214,"language":215,"meta":216,"style":216},"language-python shiki shiki-themes github-light github-dark","# Django REST Framework — serializer + view + URL wiring\nimport os\nfrom rest_framework import serializers, status\nfrom rest_framework.decorators import api_view\nfrom rest_framework.response import Response\n\nMAX_NAME_LEN = int(os.getenv(\"ITEM_NAME_MAX\", \"64\"))\n\n\nclass ItemSerializer(serializers.Serializer):\n    name = serializers.CharField(max_length=MAX_NAME_LEN)\n    price_cents = serializers.IntegerField(min_value=0)\n\n\n@api_view([\"POST\"])\ndef create_item(request):\n    serializer = ItemSerializer(data=request.data)\n    serializer.is_valid(raise_exception=True)\n    data = serializer.validated_data\n    return Response(\n        {\"name\": data[\"name\"], \"price_cents\": data[\"price_cents\"]},\n        status=status.HTTP_201_CREATED,\n    )\n","python","",[48,218,219,228,239,253,266,279,286,315,320,325,349,372,392,397,402,417,429,448,464,475,484,511,528],{"__ignoreMap":216},[220,221,224],"span",{"class":222,"line":223},"line",1,[220,225,227],{"class":226},"sJ8bj","# Django REST Framework — serializer + view + URL wiring\n",[220,229,231,235],{"class":222,"line":230},2,[220,232,234],{"class":233},"szBVR","import",[220,236,238],{"class":237},"sVt8B"," os\n",[220,240,242,245,248,250],{"class":222,"line":241},3,[220,243,244],{"class":233},"from",[220,246,247],{"class":237}," rest_framework ",[220,249,234],{"class":233},[220,251,252],{"class":237}," serializers, status\n",[220,254,256,258,261,263],{"class":222,"line":255},4,[220,257,244],{"class":233},[220,259,260],{"class":237}," rest_framework.decorators ",[220,262,234],{"class":233},[220,264,265],{"class":237}," api_view\n",[220,267,269,271,274,276],{"class":222,"line":268},5,[220,270,244],{"class":233},[220,272,273],{"class":237}," rest_framework.response ",[220,275,234],{"class":233},[220,277,278],{"class":237}," Response\n",[220,280,282],{"class":222,"line":281},6,[220,283,285],{"emptyLinePlaceholder":284},true,"\n",[220,287,289,293,296,299,302,306,309,312],{"class":222,"line":288},7,[220,290,292],{"class":291},"sj4cs","MAX_NAME_LEN",[220,294,295],{"class":233}," =",[220,297,298],{"class":291}," int",[220,300,301],{"class":237},"(os.getenv(",[220,303,305],{"class":304},"sZZnC","\"ITEM_NAME_MAX\"",[220,307,308],{"class":237},", ",[220,310,311],{"class":304},"\"64\"",[220,313,314],{"class":237},"))\n",[220,316,318],{"class":222,"line":317},8,[220,319,285],{"emptyLinePlaceholder":284},[220,321,323],{"class":222,"line":322},9,[220,324,285],{"emptyLinePlaceholder":284},[220,326,328,331,335,338,341,343,346],{"class":222,"line":327},10,[220,329,330],{"class":233},"class",[220,332,334],{"class":333},"sScJk"," ItemSerializer",[220,336,337],{"class":237},"(",[220,339,340],{"class":333},"serializers",[220,342,27],{"class":237},[220,344,345],{"class":333},"Serializer",[220,347,348],{"class":237},"):\n",[220,350,352,355,358,361,365,367,369],{"class":222,"line":351},11,[220,353,354],{"class":237},"    name ",[220,356,357],{"class":233},"=",[220,359,360],{"class":237}," serializers.CharField(",[220,362,364],{"class":363},"s4XuR","max_length",[220,366,357],{"class":233},[220,368,292],{"class":291},[220,370,371],{"class":237},")\n",[220,373,375,378,380,383,386,388,390],{"class":222,"line":374},12,[220,376,377],{"class":237},"    price_cents ",[220,379,357],{"class":233},[220,381,382],{"class":237}," serializers.IntegerField(",[220,384,385],{"class":363},"min_value",[220,387,357],{"class":233},[220,389,82],{"class":291},[220,391,371],{"class":237},[220,393,395],{"class":222,"line":394},13,[220,396,285],{"emptyLinePlaceholder":284},[220,398,400],{"class":222,"line":399},14,[220,401,285],{"emptyLinePlaceholder":284},[220,403,405,408,411,414],{"class":222,"line":404},15,[220,406,407],{"class":333},"@api_view",[220,409,410],{"class":237},"([",[220,412,413],{"class":304},"\"POST\"",[220,415,416],{"class":237},"])\n",[220,418,420,423,426],{"class":222,"line":419},16,[220,421,422],{"class":233},"def",[220,424,425],{"class":333}," create_item",[220,427,428],{"class":237},"(request):\n",[220,430,432,435,437,440,443,445],{"class":222,"line":431},17,[220,433,434],{"class":237},"    serializer ",[220,436,357],{"class":233},[220,438,439],{"class":237}," ItemSerializer(",[220,441,442],{"class":363},"data",[220,444,357],{"class":233},[220,446,447],{"class":237},"request.data)\n",[220,449,451,454,457,459,462],{"class":222,"line":450},18,[220,452,453],{"class":237},"    serializer.is_valid(",[220,455,456],{"class":363},"raise_exception",[220,458,357],{"class":233},[220,460,461],{"class":291},"True",[220,463,371],{"class":237},[220,465,467,470,472],{"class":222,"line":466},19,[220,468,469],{"class":237},"    data ",[220,471,357],{"class":233},[220,473,474],{"class":237}," serializer.validated_data\n",[220,476,478,481],{"class":222,"line":477},20,[220,479,480],{"class":233},"    return",[220,482,483],{"class":237}," Response(\n",[220,485,487,490,493,496,498,501,504,506,508],{"class":222,"line":486},21,[220,488,489],{"class":237},"        {",[220,491,492],{"class":304},"\"name\"",[220,494,495],{"class":237},": data[",[220,497,492],{"class":304},[220,499,500],{"class":237},"], ",[220,502,503],{"class":304},"\"price_cents\"",[220,505,495],{"class":237},[220,507,503],{"class":304},[220,509,510],{"class":237},"]},\n",[220,512,514,517,519,522,525],{"class":222,"line":513},22,[220,515,516],{"class":363},"        status",[220,518,357],{"class":233},[220,520,521],{"class":237},"status.",[220,523,524],{"class":291},"HTTP_201_CREATED",[220,526,527],{"class":237},",\n",[220,529,531],{"class":222,"line":530},23,[220,532,533],{"class":237},"    )\n",[14,535,536,537,540],{},"The DRF version runs synchronously under WSGI. The serializer is a separate object you instantiate, validate, then read ",[48,538,539],{},"validated_data"," from. In exchange you get a browsable API, an admin you can register the model into, and the whole Django request\u002Fresponse machinery — throttling, permissions, pagination — available as class attributes rather than code.",[211,542,544],{"className":213,"code":543,"language":215,"meta":216,"style":216},"# FastAPI — types are the contract\nimport os\nfrom fastapi import FastAPI, status\nfrom pydantic import BaseModel, Field\n\nMAX_NAME_LEN = int(os.getenv(\"ITEM_NAME_MAX\", \"64\"))\napp = FastAPI()\n\n\nclass Item(BaseModel):\n    name: str = Field(max_length=MAX_NAME_LEN)\n    price_cents: int = Field(ge=0)\n\n\n@app.post(\"\u002Fitems\", status_code=status.HTTP_201_CREATED)\nasync def create_item(item: Item) -> Item:\n    return item\n",[48,545,546,551,557,569,581,585,603,613,617,621,635,656,677,681,685,708,721],{"__ignoreMap":216},[220,547,548],{"class":222,"line":223},[220,549,550],{"class":226},"# FastAPI — types are the contract\n",[220,552,553,555],{"class":222,"line":230},[220,554,234],{"class":233},[220,556,238],{"class":237},[220,558,559,561,564,566],{"class":222,"line":241},[220,560,244],{"class":233},[220,562,563],{"class":237}," fastapi ",[220,565,234],{"class":233},[220,567,568],{"class":237}," FastAPI, status\n",[220,570,571,573,576,578],{"class":222,"line":255},[220,572,244],{"class":233},[220,574,575],{"class":237}," pydantic ",[220,577,234],{"class":233},[220,579,580],{"class":237}," BaseModel, Field\n",[220,582,583],{"class":222,"line":268},[220,584,285],{"emptyLinePlaceholder":284},[220,586,587,589,591,593,595,597,599,601],{"class":222,"line":281},[220,588,292],{"class":291},[220,590,295],{"class":233},[220,592,298],{"class":291},[220,594,301],{"class":237},[220,596,305],{"class":304},[220,598,308],{"class":237},[220,600,311],{"class":304},[220,602,314],{"class":237},[220,604,605,608,610],{"class":222,"line":288},[220,606,607],{"class":237},"app ",[220,609,357],{"class":233},[220,611,612],{"class":237}," FastAPI()\n",[220,614,615],{"class":222,"line":317},[220,616,285],{"emptyLinePlaceholder":284},[220,618,619],{"class":222,"line":322},[220,620,285],{"emptyLinePlaceholder":284},[220,622,623,625,628,630,633],{"class":222,"line":327},[220,624,330],{"class":233},[220,626,627],{"class":333}," Item",[220,629,337],{"class":237},[220,631,632],{"class":333},"BaseModel",[220,634,348],{"class":237},[220,636,637,640,643,645,648,650,652,654],{"class":222,"line":351},[220,638,639],{"class":237},"    name: ",[220,641,642],{"class":291},"str",[220,644,295],{"class":233},[220,646,647],{"class":237}," Field(",[220,649,364],{"class":363},[220,651,357],{"class":233},[220,653,292],{"class":291},[220,655,371],{"class":237},[220,657,658,661,664,666,668,671,673,675],{"class":222,"line":374},[220,659,660],{"class":237},"    price_cents: ",[220,662,663],{"class":291},"int",[220,665,295],{"class":233},[220,667,647],{"class":237},[220,669,670],{"class":363},"ge",[220,672,357],{"class":233},[220,674,82],{"class":291},[220,676,371],{"class":237},[220,678,679],{"class":222,"line":394},[220,680,285],{"emptyLinePlaceholder":284},[220,682,683],{"class":222,"line":399},[220,684,285],{"emptyLinePlaceholder":284},[220,686,687,690,692,695,697,700,702,704,706],{"class":222,"line":404},[220,688,689],{"class":333},"@app.post",[220,691,337],{"class":237},[220,693,694],{"class":304},"\"\u002Fitems\"",[220,696,308],{"class":237},[220,698,699],{"class":363},"status_code",[220,701,357],{"class":233},[220,703,521],{"class":237},[220,705,524],{"class":291},[220,707,371],{"class":237},[220,709,710,713,716,718],{"class":222,"line":419},[220,711,712],{"class":233},"async",[220,714,715],{"class":233}," def",[220,717,425],{"class":333},[220,719,720],{"class":237},"(item: Item) -> Item:\n",[220,722,723,725],{"class":222,"line":431},[220,724,480],{"class":233},[220,726,727],{"class":237}," item\n",[14,729,730,731,733,734,738],{},"The FastAPI handler is ",[48,732,712],{},", validation is the type annotation itself, and the OpenAPI schema plus interactive docs are generated for free. There is no separate serializer step and no URL routing file — the decorator is the route. The commercial consequence shows up later: that generated schema is what feeds client SDK generators and the reference pages your customers read, so you can polish it by ",[18,735,737],{"href":736},"\u002Fgetting-started-with-python-apis-for-builders\u002Fdocumenting-apis-with-openapi\u002Fcustomizing-fastapi-openapi-schema\u002F","customizing the FastAPI OpenAPI schema"," instead of maintaining docs by hand. DRF can emit an equivalent document through drf-spectacular, but you install it, configure it, and annotate views that the generator cannot infer.",[29,740],{},[32,742,744],{"id":743},"where-the-request-time-actually-goes","Where the Request Time Actually Goes",[14,746,747],{},"Framework benchmarks measure empty handlers, which is not your workload. Take a realistic paid endpoint: validate the body (about 4 ms of CPU with Pydantic), read one indexed row from Postgres (8 ms), call a vendor API such as a payment processor or a model provider (150 ms), then serialize the response (about 4 ms). That is 166 ms of wall clock, of which 158 ms is pure waiting.",[61,749,70,754,70,757,70,760,70,763,70,769,70,775,70,783,70,787,70,790,70,793,70,798,70,803,70,806,70,811,70,815,70,817,70,821,70,825,70,829,70,833,70,837,70,842,70,846,70,850],{"viewBox":750,"role":64,"ariaLabelledBy":751,"xmlns":68,"style":69},"0 0 720 292",[752,753],"fdrf-budget-t","fdrf-budget-d",[72,755,756],{"id":752},"Where one request's 166 milliseconds are spent in each framework",[76,758,759],{"id":753},"A synchronous DRF worker stays occupied for the whole 166 milliseconds, serving about six requests per second, while a FastAPI event loop only holds the CPU for 8 milliseconds and serves other requests during the waits.",[80,761],{"x":82,"y":82,"width":83,"height":762,"fill":85},"292",[95,764,768],{"x":765,"y":766,"fill":99,"style":767},"360","26","text-anchor:middle;font-size:14;font-family:var(--font-sans);","One request: 8 ms CPU, 8 ms Postgres, 150 ms vendor call",[95,770,774],{"x":771,"y":772,"fill":99,"style":773},"30","60","text-anchor:start;font-size:12;font-family:var(--font-sans);","DRF worker — occupied for the full 166 ms",[80,776],{"x":777,"y":778,"width":779,"height":780,"rx":781,"fill":115,"stroke":782,"style":93},"95","70","581","36","4","var(--c-coral)",[222,784],{"x1":785,"y1":778,"x2":785,"y2":120,"stroke":782,"style":786},"109","stroke-width:1;",[222,788],{"x1":789,"y1":778,"x2":789,"y2":120,"stroke":782,"style":786},"137",[222,791],{"x1":792,"y1":778,"x2":792,"y2":120,"stroke":782,"style":786},"662",[95,794,797],{"x":795,"y":796,"fill":99,"style":121},"400","93","vendor wait — thread still held",[95,799,802],{"x":777,"y":800,"fill":105,"style":801},"126","text-anchor:start;font-size:11;font-family:var(--font-sans);","about 6 requests per second per worker",[95,804,805],{"x":771,"y":132,"fill":99,"style":773},"FastAPI loop — holds the CPU for 8 ms only",[80,807],{"x":777,"y":808,"width":809,"height":780,"rx":810,"fill":115,"stroke":151,"style":93},"174","14","3",[80,812],{"x":785,"y":808,"width":813,"height":780,"rx":810,"fill":85,"stroke":116,"style":814},"553","stroke-width:2;stroke-dasharray:6 4;",[80,816],{"x":792,"y":808,"width":809,"height":780,"rx":810,"fill":115,"stroke":151,"style":93},[95,818,820],{"x":795,"y":819,"fill":99,"style":121},"197","loop serves other requests",[95,822,824],{"x":777,"y":823,"fill":105,"style":801},"230","about 250 requests per second per worker",[222,826],{"x1":777,"y1":827,"x2":828,"y2":827,"stroke":116,"style":786},"248","676",[95,830,832],{"x":777,"y":831,"fill":105,"style":801},"266","0 ms",[95,834,836],{"x":828,"y":831,"fill":105,"style":835},"text-anchor:end;font-size:11;font-family:var(--font-sans);","166 ms",[80,838],{"x":771,"y":839,"width":91,"height":840,"rx":841,"fill":115,"stroke":151,"style":93},"278","10","2",[95,843,845],{"x":162,"y":844,"fill":105,"style":801},"287","CPU held",[80,847],{"x":848,"y":839,"width":91,"height":840,"rx":841,"fill":85,"stroke":116,"style":849},"140","stroke-width:2;stroke-dasharray:4 3;",[95,851,853],{"x":852,"y":844,"fill":105,"style":801},"160","waiting, worker free",[14,855,856,857,861],{},"A synchronous DRF worker is pinned for the entire 166 ms, so one worker tops out near 6 requests per second. A four-vCPU box running nine Gunicorn workers therefore ceilings around 55 requests per second, and each worker carries roughly 110 MB of resident memory because it loads the whole Django app. FastAPI holds the loop only for the 8 ms of actual CPU, so one worker approaches 250 requests per second before the core saturates, and four workers on the same box handle several hundred. To absorb a 300 request-per-second peak you rent six boxes for DRF or one for FastAPI — on a typical 4 vCPU plan that is roughly $510 a month against $85. Run those numbers against your own traffic with the method in ",[18,858,860],{"href":859},"\u002Fbuilding-monetizing-api-driven-micro-saas\u002Fdesigning-api-pricing-tiers\u002Fcalculating-cost-per-api-request\u002F","calculating cost per API request"," before you believe the ratio.",[14,863,864,865,869,870,27],{},"Flip the workload and the argument collapses. On DB-bound CRUD — a 3 ms indexed query, 2 ms of serialization, no upstream call — the database is the bottleneck and a tuned DRF deployment lands within about 20 percent of FastAPI. Async buys you nothing when there is nothing to wait for. It is the fan-out endpoints, the ones that call ",[18,866,868],{"href":867},"\u002Fgetting-started-with-python-apis-for-builders\u002Fmaking-http-requests-with-requests-library\u002Fhttpx-vs-requests-for-async\u002F","upstream APIs over httpx",", that make the difference show up on the invoice. Whichever you pick, the worker count matters as much as the framework; size it with ",[18,871,873],{"href":872},"\u002Fgetting-started-with-python-apis-for-builders\u002Fsetting-up-fastapi\u002Fuvicorn-vs-gunicorn-worker-configuration\u002F","Uvicorn vs Gunicorn worker configuration",[29,875],{},[32,877,879],{"id":878},"the-database-layer-decides-more-than-the-router","The Database Layer Decides More Than the Router",[14,881,882],{},"The router is the small half of this decision. The data layer is the large half, because that is where async either pays off or quietly does not.",[14,884,885,886,308,889,308,892,895,896,899],{},"Django's ORM has an async API — ",[48,887,888],{},"await Item.objects.aget(...)",[48,890,891],{},"await Item.objects.acreate(...)",[48,893,894],{},"async for row in qs"," — but underneath it still hands the query to a thread executor rather than talking to Postgres over an async driver. You get a coroutine-shaped call, not a non-blocking database round trip. Touch the ORM synchronously inside an async view and Django raises ",[48,897,898],{},"SynchronousOnlyOperation",", which is the framework telling you the two models do not mix by accident.",[61,901,70,905,70,908,70,911,70,913,70,916,70,921,70,926,70,929,70,933,70,936,70,941,70,944,70,948,70,951,70,954,70,958,70,961,70,965,70,968,70,971,70,975,70,978,70,982,70,985,70,987,70,990,70,993,70,996,70,999,70,1001,70,1004,70,1008,70,1010,70,1013,70,1015,70,1017,70,1020,70,1022,70,1025,70,1027,70,1030,70,1033,70,1035,70,1038,70,1043,70,1049,70,1053,70,1057],{"viewBox":63,"role":64,"ariaLabelledBy":902,"xmlns":68,"style":69},[903,904],"fdrf-data-t","fdrf-data-d",[72,906,907],{"id":903},"Request path to Postgres in each stack",[76,909,910],{"id":904},"DRF routes a request through a WSGI worker thread, a serializer, and the synchronous Django ORM, while FastAPI routes it through the ASGI event loop, a Pydantic model, and async SQLAlchemy over asyncpg to the same database.",[80,912],{"x":82,"y":82,"width":83,"height":84,"fill":85},[95,914,101],{"x":88,"y":771,"fill":99,"style":915},"text-anchor:start;font-size:13;font-family:var(--font-sans);",[80,917],{"x":88,"y":110,"width":918,"height":919,"rx":920,"fill":115,"stroke":92,"style":93},"115","52","8",[95,922,925],{"x":923,"y":924,"fill":99,"style":121},"77","75","HTTP request",[222,927],{"x1":928,"y1":778,"x2":128,"y2":778,"stroke":116,"style":93},"135",[930,931],"polygon",{"points":932,"fill":116},"150,70 141,65 141,75",[80,934],{"x":935,"y":110,"width":918,"height":919,"rx":920,"fill":85,"stroke":116,"style":93},"150",[95,937,940],{"x":938,"y":939,"fill":99,"style":121},"207","67","WSGI worker",[95,942,943],{"x":938,"y":111,"fill":105,"style":121},"thread",[222,945],{"x1":946,"y1":778,"x2":947,"y2":778,"stroke":116,"style":93},"265","276",[930,949],{"points":950,"fill":116},"280,70 271,65 271,75",[80,952],{"x":953,"y":110,"width":918,"height":919,"rx":920,"fill":85,"stroke":116,"style":93},"280",[95,955,957],{"x":956,"y":939,"fill":99,"style":121},"337","DRF",[95,959,960],{"x":956,"y":111,"fill":105,"style":121},"serializer",[222,962],{"x1":963,"y1":778,"x2":964,"y2":778,"stroke":116,"style":93},"395","406",[930,966],{"points":967,"fill":116},"410,70 401,65 401,75",[80,969],{"x":970,"y":110,"width":918,"height":919,"rx":920,"fill":85,"stroke":782,"style":93},"410",[95,972,974],{"x":973,"y":939,"fill":99,"style":121},"467","Django ORM",[95,976,977],{"x":973,"y":111,"fill":105,"style":121},"sync driver",[222,979],{"x1":980,"y1":778,"x2":981,"y2":778,"stroke":116,"style":93},"525","556",[930,983],{"points":984,"fill":116},"560,70 551,65 551,75",[95,986,155],{"x":88,"y":178,"fill":99,"style":915},[80,988],{"x":88,"y":989,"width":918,"height":919,"rx":920,"fill":115,"stroke":151,"style":93},"184",[95,991,925],{"x":923,"y":992,"fill":99,"style":121},"215",[222,994],{"x1":928,"y1":995,"x2":128,"y2":995,"stroke":116,"style":93},"210",[930,997],{"points":998,"fill":116},"150,210 141,205 141,215",[80,1000],{"x":935,"y":989,"width":918,"height":919,"rx":920,"fill":85,"stroke":116,"style":93},[95,1002,1003],{"x":938,"y":938,"fill":99,"style":121},"ASGI event",[95,1005,1007],{"x":938,"y":1006,"fill":105,"style":121},"224","loop",[222,1009],{"x1":946,"y1":995,"x2":947,"y2":995,"stroke":116,"style":93},[930,1011],{"points":1012,"fill":116},"280,210 271,205 271,215",[80,1014],{"x":953,"y":989,"width":918,"height":919,"rx":920,"fill":85,"stroke":116,"style":93},[95,1016,45],{"x":956,"y":938,"fill":99,"style":121},[95,1018,1019],{"x":956,"y":1006,"fill":105,"style":121},"model",[222,1021],{"x1":963,"y1":995,"x2":964,"y2":995,"stroke":116,"style":93},[930,1023],{"points":1024,"fill":116},"410,210 401,205 401,215",[80,1026],{"x":970,"y":989,"width":918,"height":919,"rx":920,"fill":85,"stroke":151,"style":93},[95,1028,1029],{"x":973,"y":938,"fill":99,"style":121},"SQLAlchemy",[95,1031,1032],{"x":973,"y":1006,"fill":105,"style":121},"+ asyncpg",[222,1034],{"x1":980,"y1":995,"x2":981,"y2":995,"stroke":116,"style":93},[930,1036],{"points":1037,"fill":116},"560,210 551,205 551,215",[80,1039],{"x":1040,"y":110,"width":1041,"height":1042,"rx":840,"fill":115,"stroke":92,"style":93},"562","138","192",[95,1044,1048],{"x":1045,"y":1046,"fill":99,"style":1047},"631","132","text-anchor:middle;font-size:13;font-family:var(--font-sans);","Postgres",[95,1050,1052],{"x":1045,"y":1051,"fill":105,"style":106},"152","one shared database",[95,1054,1056],{"x":88,"y":1055,"fill":105,"style":801},"270","Django's async ORM calls still run the query in a thread executor, not on an async driver.",[95,1058,1060],{"x":88,"y":1059,"fill":105,"style":801},"288","Both stacks can point at the same tables during a gradual migration.",[14,1062,1063,1064,27],{},"FastAPI plus SQLAlchemy's async engine talks to Postgres over asyncpg, so a query genuinely yields the loop. That is the configuration worth copying, and the pool settings are the part people skip. Read them from the environment so staging and production differ by config, not by code — the same discipline described in ",[18,1065,1067],{"href":1066},"\u002Fscaling-and-operating-production-python-apis\u002Fasync-database-access-with-sqlalchemy\u002F","async database access with SQLAlchemy",[211,1069,1071],{"className":213,"code":1070,"language":215,"meta":216,"style":216},"# db.py — async engine and session dependency, all config from the environment\nimport os\nfrom collections.abc import AsyncIterator\n\nfrom sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine\n\nengine = create_async_engine(\n    os.environ[\"DATABASE_URL\"],  # postgresql+asyncpg:\u002F\u002F...\n    pool_size=int(os.getenv(\"DB_POOL_SIZE\", \"10\")),\n    max_overflow=int(os.getenv(\"DB_MAX_OVERFLOW\", \"5\")),\n    pool_timeout=float(os.getenv(\"DB_POOL_TIMEOUT\", \"5\")),\n    pool_recycle=int(os.getenv(\"DB_POOL_RECYCLE\", \"1800\")),\n    echo=os.getenv(\"DB_ECHO\", \"0\") == \"1\",\n)\nSession = async_sessionmaker(engine, expire_on_commit=False)\n\n\nasync def get_session() -> AsyncIterator[AsyncSession]:\n    async with Session() as session:\n        yield session\n",[48,1072,1073,1078,1084,1096,1100,1112,1116,1126,1140,1162,1183,1204,1225,1254,1258,1278,1282,1286,1298,1315],{"__ignoreMap":216},[220,1074,1075],{"class":222,"line":223},[220,1076,1077],{"class":226},"# db.py — async engine and session dependency, all config from the environment\n",[220,1079,1080,1082],{"class":222,"line":230},[220,1081,234],{"class":233},[220,1083,238],{"class":237},[220,1085,1086,1088,1091,1093],{"class":222,"line":241},[220,1087,244],{"class":233},[220,1089,1090],{"class":237}," collections.abc ",[220,1092,234],{"class":233},[220,1094,1095],{"class":237}," AsyncIterator\n",[220,1097,1098],{"class":222,"line":255},[220,1099,285],{"emptyLinePlaceholder":284},[220,1101,1102,1104,1107,1109],{"class":222,"line":268},[220,1103,244],{"class":233},[220,1105,1106],{"class":237}," sqlalchemy.ext.asyncio ",[220,1108,234],{"class":233},[220,1110,1111],{"class":237}," AsyncSession, async_sessionmaker, create_async_engine\n",[220,1113,1114],{"class":222,"line":281},[220,1115,285],{"emptyLinePlaceholder":284},[220,1117,1118,1121,1123],{"class":222,"line":288},[220,1119,1120],{"class":237},"engine ",[220,1122,357],{"class":233},[220,1124,1125],{"class":237}," create_async_engine(\n",[220,1127,1128,1131,1134,1137],{"class":222,"line":317},[220,1129,1130],{"class":237},"    os.environ[",[220,1132,1133],{"class":304},"\"DATABASE_URL\"",[220,1135,1136],{"class":237},"],  ",[220,1138,1139],{"class":226},"# postgresql+asyncpg:\u002F\u002F...\n",[220,1141,1142,1145,1147,1149,1151,1154,1156,1159],{"class":222,"line":322},[220,1143,1144],{"class":363},"    pool_size",[220,1146,357],{"class":233},[220,1148,663],{"class":291},[220,1150,301],{"class":237},[220,1152,1153],{"class":304},"\"DB_POOL_SIZE\"",[220,1155,308],{"class":237},[220,1157,1158],{"class":304},"\"10\"",[220,1160,1161],{"class":237},")),\n",[220,1163,1164,1167,1169,1171,1173,1176,1178,1181],{"class":222,"line":327},[220,1165,1166],{"class":363},"    max_overflow",[220,1168,357],{"class":233},[220,1170,663],{"class":291},[220,1172,301],{"class":237},[220,1174,1175],{"class":304},"\"DB_MAX_OVERFLOW\"",[220,1177,308],{"class":237},[220,1179,1180],{"class":304},"\"5\"",[220,1182,1161],{"class":237},[220,1184,1185,1188,1190,1193,1195,1198,1200,1202],{"class":222,"line":351},[220,1186,1187],{"class":363},"    pool_timeout",[220,1189,357],{"class":233},[220,1191,1192],{"class":291},"float",[220,1194,301],{"class":237},[220,1196,1197],{"class":304},"\"DB_POOL_TIMEOUT\"",[220,1199,308],{"class":237},[220,1201,1180],{"class":304},[220,1203,1161],{"class":237},[220,1205,1206,1209,1211,1213,1215,1218,1220,1223],{"class":222,"line":374},[220,1207,1208],{"class":363},"    pool_recycle",[220,1210,357],{"class":233},[220,1212,663],{"class":291},[220,1214,301],{"class":237},[220,1216,1217],{"class":304},"\"DB_POOL_RECYCLE\"",[220,1219,308],{"class":237},[220,1221,1222],{"class":304},"\"1800\"",[220,1224,1161],{"class":237},[220,1226,1227,1230,1232,1235,1238,1240,1243,1246,1249,1252],{"class":222,"line":394},[220,1228,1229],{"class":363},"    echo",[220,1231,357],{"class":233},[220,1233,1234],{"class":237},"os.getenv(",[220,1236,1237],{"class":304},"\"DB_ECHO\"",[220,1239,308],{"class":237},[220,1241,1242],{"class":304},"\"0\"",[220,1244,1245],{"class":237},") ",[220,1247,1248],{"class":233},"==",[220,1250,1251],{"class":304}," \"1\"",[220,1253,527],{"class":237},[220,1255,1256],{"class":222,"line":399},[220,1257,371],{"class":237},[220,1259,1260,1263,1265,1268,1271,1273,1276],{"class":222,"line":404},[220,1261,1262],{"class":237},"Session ",[220,1264,357],{"class":233},[220,1266,1267],{"class":237}," async_sessionmaker(engine, ",[220,1269,1270],{"class":363},"expire_on_commit",[220,1272,357],{"class":233},[220,1274,1275],{"class":291},"False",[220,1277,371],{"class":237},[220,1279,1280],{"class":222,"line":419},[220,1281,285],{"emptyLinePlaceholder":284},[220,1283,1284],{"class":222,"line":431},[220,1285,285],{"emptyLinePlaceholder":284},[220,1287,1288,1290,1292,1295],{"class":222,"line":450},[220,1289,712],{"class":233},[220,1291,715],{"class":233},[220,1293,1294],{"class":333}," get_session",[220,1296,1297],{"class":237},"() -> AsyncIterator[AsyncSession]:\n",[220,1299,1300,1303,1306,1309,1312],{"class":222,"line":466},[220,1301,1302],{"class":233},"    async",[220,1304,1305],{"class":233}," with",[220,1307,1308],{"class":237}," Session() ",[220,1310,1311],{"class":233},"as",[220,1313,1314],{"class":237}," session:\n",[220,1316,1317,1320],{"class":222,"line":477},[220,1318,1319],{"class":233},"        yield",[220,1321,1322],{"class":237}," session\n",[14,1324,1325,1326,1329,1330,1334,1335,1339],{},"Multiply ",[48,1327,1328],{},"DB_POOL_SIZE"," by your worker count before you set it: four workers at a pool of 10 plus 5 overflow can open 60 connections, and a small managed Postgres often caps at 100. Get that wrong and you meet the symptoms in ",[18,1331,1333],{"href":1332},"\u002Fscaling-and-operating-production-python-apis\u002Fasync-database-access-with-sqlalchemy\u002Ffixing-connection-pool-exhaustion\u002F","fixing connection pool exhaustion",". If you are still choosing a driver, ",[18,1336,1338],{"href":1337},"\u002Fscaling-and-operating-production-python-apis\u002Fasync-database-access-with-sqlalchemy\u002Fasyncpg-vs-psycopg3-for-fastapi\u002F","asyncpg vs psycopg3 for FastAPI"," settles it.",[29,1341],{},[32,1343,1345],{"id":1344},"failure-modes-that-bite-in-production","Failure Modes That Bite in Production",[14,1347,1348],{},"Each stack has a characteristic way of falling over, and both are cheap to avoid once you know them.",[14,1350,1351,1352,1355,1356,1359,1360,27],{},"DRF's classic wound is the N+1 query inside a nested serializer: a list endpoint that looks instant on 20 seeded rows issues 201 queries against 200 real ones. ",[48,1353,1354],{},"select_related"," and ",[48,1357,1358],{},"prefetch_related"," on the view's queryset fix it, but nothing warns you — the endpoint just gets slower as the customer's data grows, which means it degrades exactly when the account becomes valuable. Its second wound is throttling. DRF's built-in throttle classes count against your cache backend, so with the default per-process local memory cache and nine workers, a \"100 per hour\" limit is really 900 per hour. Point throttling at a shared Redis backend or enforce limits at the edge, following ",[18,1361,1363],{"href":1362},"\u002Fgetting-started-with-python-apis-for-builders\u002Fmaking-http-requests-with-requests-library\u002Fbest-practices-for-api-rate-limiting\u002F","API rate limiting best practices",[14,1365,1366,1367,1370,1371,1373,1374,1377,1378,27],{},"FastAPI's classic wound is a blocking call inside an async handler. One ",[48,1368,1369],{},"time.sleep",", one synchronous database driver, one CPU-heavy PDF render, and the whole event loop stalls for every concurrent request — a single 400 ms blocking call turns into 400 ms of added latency for everyone in flight. Declaring the handler ",[48,1372,422],{}," instead of ",[48,1375,1376],{},"async def"," moves it to a worker threadpool, which caps at 40 threads by default, so a slow blocking route silently becomes a queue. The fix is boring and effective: keep async routes non-blocking, push heavy work to a background queue, and assert the behaviour under load with ",[18,1379,1381],{"href":1380},"\u002Fscaling-and-operating-production-python-apis\u002Ftesting-python-apis-with-pytest\u002Ftesting-async-fastapi-endpoints-with-httpx\u002F","testing async FastAPI endpoints with httpx",[1383,1384,1385,1401],"table",{},[1386,1387,1388],"thead",{},[1389,1390,1391,1395,1398],"tr",{},[1392,1393,1394],"th",{},"Risk",[1392,1396,1397],{},"DRF symptom",[1392,1399,1400],{},"FastAPI symptom",[1402,1403,1404,1416,1427,1438],"tbody",{},[1389,1405,1406,1410,1413],{},[1407,1408,1409],"td",{},"Data growth",[1407,1411,1412],{},"Nested N+1 queries",[1407,1414,1415],{},"Slow unindexed query",[1389,1417,1418,1421,1424],{},[1407,1419,1420],{},"Concurrency",[1407,1422,1423],{},"Worker starvation",[1407,1425,1426],{},"Blocked event loop",[1389,1428,1429,1432,1435],{},[1407,1430,1431],{},"Limits",[1407,1433,1434],{},"Per-process throttle drift",[1407,1436,1437],{},"Threadpool queue at 40",[1389,1439,1440,1443,1446],{},[1407,1441,1442],{},"Deploys",[1407,1444,1445],{},"Large image, slow boot",[1407,1447,1448],{},"Fast boot, more assembly",[14,1450,1451,1452,27],{},"Image size follows the same pattern. A Django plus DRF image lands near 400 MB before your own code, while a FastAPI service with SQLAlchemy and httpx sits closer to 180 MB. That is 90 seconds against 25 on a cold rollout, which matters when you deploy six times a day — see ",[18,1453,1455],{"href":1454},"\u002Fscaling-and-operating-production-python-apis\u002Fcontainerizing-python-apis-with-docker\u002Foptimizing-python-docker-image-size\u002F","optimizing Python Docker image size",[29,1457],{},[32,1459,1461],{"id":1460},"when-to-choose-each","When to Choose Each",[14,1463,1464],{},[1465,1466,1467],"strong",{},"Reach for Django REST Framework when:",[1469,1470,1471,1475,1478,1481],"ul",{},[1472,1473,1474],"li",{},"You already have a Django app and want to expose part of it as an API — bolting DRF onto existing models is the path of least resistance, and rewriting in FastAPI would be pure cost.",[1472,1476,1477],{},"Your product needs a relational data model with an admin panel for non-technical operators on day one.",[1472,1479,1480],{},"You want auth, permissions, throttling, and pagination as configuration rather than code you assemble.",[1472,1482,1483],{},"The team knows Django and shipping velocity matters more than raw async throughput.",[14,1485,1486],{},[1465,1487,1488],{},"Reach for FastAPI when:",[1469,1490,1491,1494,1497,1500],{},[1472,1492,1493],{},"You are starting fresh and the API is the product, not a layer over a website.",[1472,1495,1496],{},"Workloads are I\u002FO-bound: heavy upstream orchestration, streaming responses, or websockets.",[1472,1498,1499],{},"You want typed request and response contracts plus generated docs without extra libraries.",[1472,1501,1502],{},"You prefer to pick your own ORM and keep the dependency tree small for cheaper, faster deploys.",[14,1504,1505,1506,1510],{},"The deciding question is rarely \"which is faster\" — it is \"does an existing Django app already anchor me.\" A live Django codebase makes DRF the right call almost every time; a greenfield commercial API makes FastAPI's lean async core the stronger default. If you want a third option in the frame, ",[18,1507,1509],{"href":1508},"\u002Fgetting-started-with-python-apis-for-builders\u002Fsetting-up-fastapi\u002Ffastapi-vs-litestar\u002F","FastAPI vs Litestar"," covers the newer challenger.",[29,1512],{},[32,1514,1516],{"id":1515},"migration-path","Migration Path",[14,1518,1519],{},"If you are on DRF and feel the async ceiling, you do not need a big-bang rewrite. Stand up a FastAPI service alongside the Django app, point it at the same database, and move endpoints across one at a time as their concurrency justifies it.",[61,1521,70,1526,70,1529,70,1532,70,1535,70,1539,70,1542,70,1547,70,1551,70,1554,70,1557,70,1560,70,1563,70,1567,70,1570,70,1573,70,1577,70,1580,70,1584,70,1587,70,1590,70,1592,70,1594,70,1596,70,1599,70,1602,70,1605,70,1608,70,1611,70,1614,70,1617,70,1620],{"viewBox":1522,"role":64,"ariaLabelledBy":1523,"xmlns":68,"style":69},"0 0 720 220",[1524,1525],"fdrf-mig-t","fdrf-mig-d",[72,1527,1528],{"id":1524},"Four-stage migration from Django REST Framework to FastAPI",[76,1530,1531],{"id":1525},"Start with Django owning every route, add a FastAPI service on the same database, move the fan-out endpoints behind a shared gateway, then leave Django owning models and the admin.",[80,1533],{"x":82,"y":82,"width":83,"height":1534,"fill":85},"220",[95,1536,1538],{"x":765,"y":1537,"fill":99,"style":767},"24","Move endpoints, not the whole codebase",[80,1540],{"x":1541,"y":173,"width":935,"height":919,"rx":920,"fill":115,"stroke":92,"style":93},"25",[95,1543,1546],{"x":1544,"y":1545,"fill":99,"style":121},"100","63","Django owns",[95,1548,1550],{"x":1544,"y":1549,"fill":99,"style":121},"80","every route",[80,1552],{"x":1553,"y":173,"width":935,"height":919,"rx":920,"fill":85,"stroke":116,"style":93},"205",[95,1555,1556],{"x":953,"y":1545,"fill":99,"style":121},"Add FastAPI on",[95,1558,1559],{"x":953,"y":1549,"fill":99,"style":121},"the same tables",[80,1561],{"x":1562,"y":173,"width":935,"height":919,"rx":920,"fill":85,"stroke":116,"style":93},"375",[95,1564,1566],{"x":1565,"y":1545,"fill":99,"style":121},"450","Shift fan-out",[95,1568,1569],{"x":1565,"y":1549,"fill":99,"style":121},"routes across",[80,1571],{"x":1572,"y":173,"width":935,"height":919,"rx":920,"fill":115,"stroke":151,"style":93},"545",[95,1574,1576],{"x":1575,"y":1545,"fill":99,"style":121},"620","Django keeps",[95,1578,1579],{"x":1575,"y":1549,"fill":99,"style":121},"models + admin",[222,1581],{"x1":1541,"y1":1582,"x2":1583,"y2":1582,"stroke":116,"style":93},"120","690",[930,1585],{"points":1586,"fill":116},"700,120 688,114 688,126",[1588,1589],"circle",{"cx":1544,"cy":1582,"r":920,"fill":92},[1588,1591],{"cx":953,"cy":1582,"r":920,"fill":85,"stroke":116,"style":93},[1588,1593],{"cx":1565,"cy":1582,"r":920,"fill":85,"stroke":116,"style":93},[1588,1595],{"cx":1575,"cy":1582,"r":920,"fill":151},[95,1597,1598],{"x":1544,"y":935,"fill":105,"style":106},"week 0",[95,1600,1601],{"x":953,"y":935,"fill":105,"style":106},"week 1",[95,1603,1604],{"x":1565,"y":935,"fill":105,"style":106},"weeks 2-8",[95,1606,1607],{"x":1575,"y":935,"fill":105,"style":106},"steady state",[95,1609,1610],{"x":1544,"y":178,"fill":105,"style":106},"one deploy unit",[95,1612,1613],{"x":953,"y":178,"fill":105,"style":106},"read-only first",[95,1615,1616],{"x":1565,"y":178,"fill":105,"style":106},"one route at a time",[95,1618,1619],{"x":1575,"y":178,"fill":105,"style":106},"two services, one DB",[95,1621,1623],{"x":765,"y":1622,"fill":105,"style":106},"202","Keep the public URLs stable; route at the edge, not in client code.",[14,1625,1626,1627,1631,1632,1636,1637,27],{},"Start with read-only endpoints: they carry no write-conflict risk, and if the new service misbehaves you route traffic back in seconds. Keep Django as the single owner of migrations so two codebases never fight over schema, and have SQLAlchemy reflect or mirror the existing tables rather than redefining them. Route both services behind one hostname so customers never see the seam — changing a published URL is a breaking change, which is why ",[18,1628,1630],{"href":1629},"\u002Fscaling-and-operating-production-python-apis\u002Fversioning-and-evolving-public-apis\u002F","versioning and evolving public APIs"," treats it as a contract question rather than a routing one. Cut over with ",[18,1633,1635],{"href":1634},"\u002Fbuilding-monetizing-api-driven-micro-saas\u002Fdeploying-apis-to-render-or-vercel\u002Fzero-downtime-deploys-for-python-apis\u002F","zero-downtime deploys"," so in-flight requests finish on the old process. Once an endpoint pays its own way, the same seam lets you carve out billable surfaces and ",[18,1638,1640],{"href":1639},"\u002Fbuilding-monetizing-api-driven-micro-saas\u002Fdesigning-api-pricing-tiers\u002Fhow-to-charge-for-api-access-using-stripe\u002F","charge for API access with Stripe",[29,1642],{},[32,1644,1646],{"id":1645},"builder-verdict","Builder Verdict",[14,1648,1649],{},"If you are building a brand-new commercial API and nothing forces your hand, start with FastAPI. The async core, typed contracts, free OpenAPI docs, and small dependency surface match how modern API products are shipped and billed, and on fan-out workloads they cut your compute bill by roughly a factor of five. The one decisive reason to pick DRF instead is an existing Django application: when you already own the models, migrations, and admin, DRF turns them into an API in an afternoon, and walking away from that leverage to chase async throughput you may not need is a poor trade. Choose FastAPI for greenfield, choose DRF when Django is already your home, and spend the hours you saved on pricing instead.",[32,1651,1653],{"id":1652},"faq","FAQ",[14,1655,1656,1659],{},[1465,1657,1658],{},"Which stack costs less to run at 10 million requests a month?","\nOn I\u002FO-heavy endpoints FastAPI wins by roughly five to one, because a synchronous worker sits idle during upstream waits while the event loop keeps serving. Ten million requests with a 150 ms vendor call need about six 4 vCPU boxes under DRF and one under FastAPI — call it $510 versus $85 a month. On plain database-bound CRUD the gap shrinks to about 20 percent and the choice stops being financial.",[14,1661,1662,1665],{},[1465,1663,1664],{},"Is it worth rewriting a working DRF API in FastAPI?","\nAlmost never as a full rewrite. Move only the endpoints whose latency profile is dominated by waiting, run both services against the same database, and keep Django as the owner of migrations and the admin. A rewrite of stable CRUD burns weeks and returns a rounding error on your infrastructure bill.",[14,1667,1668,1671],{},[1465,1669,1670],{},"Can DRF serve async endpoints if I need concurrency later?","\nPartially. Django supports async views, and DRF has been adding async support incrementally, but the ORM still executes queries in a thread executor and much of the third-party ecosystem assumes a synchronous call stack. Treat async DRF as a way to await outbound HTTP calls, not as an equal to a fully async stack.",[14,1673,1674,1677],{},[1465,1675,1676],{},"How does the choice affect how fast I can ship paid features?","\nDRF is faster for the first month when your product needs an admin, user accounts, and permissions — that is a fortnight of unbilled work you skip. FastAPI is faster from month two onward on an API-first product, because typed contracts and generated docs remove the SDK and documentation drift that otherwise eats a day per release.",[14,1679,1680,1683,1684,27],{},[1465,1681,1682],{},"Does either framework make key rotation or auth easier?","\nDRF hands you token auth, permission classes, and throttling as configuration, which is quicker to stand up. FastAPI makes you assemble them, but the dependency system keeps the checks explicit and testable per route. Either way the design questions are the same ones covered in ",[18,1685,1687],{"href":1686},"\u002Fgetting-started-with-python-apis-for-builders\u002Fhandling-api-authentication-in-python\u002F","handling API authentication in Python",[32,1689,1691],{"id":1690},"related","Related",[14,1693,1694],{},[1465,1695,1696],{},"Same track:",[1469,1698,1699,1705,1710],{},[1472,1700,1701,1704],{},[18,1702,1703],{"href":25},"FastAPI vs Flask for API Development"," — the other framework comparison most builders run first.",[1472,1706,1707,1709],{},[18,1708,1509],{"href":1508}," — what changes when serialization, not I\u002FO, dominates your profile.",[1472,1711,1712,1715],{},[18,1713,1714],{"href":872},"Uvicorn vs Gunicorn Worker Configuration"," — how to size workers so the throughput numbers above hold.",[14,1717,1718],{},[1465,1719,1720],{},"Other tracks:",[1469,1722,1723,1729],{},[1472,1724,1725,1728],{},[18,1726,1727],{"href":1066},"Async Database Access with SQLAlchemy"," — the data layer that makes FastAPI's async advantage real.",[1472,1730,1731,1734],{},[18,1732,1733],{"href":1639},"How to Charge for API Access Using Stripe"," — turning the endpoints you just moved into billable surface.",[1736,1737,1738],"style",{},"html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":216,"searchDepth":230,"depth":230,"links":1740},[1741,1742,1743,1744,1745,1746,1747,1748,1749,1750],{"id":34,"depth":230,"text":35},{"id":201,"depth":230,"text":202},{"id":743,"depth":230,"text":744},{"id":878,"depth":230,"text":879},{"id":1344,"depth":230,"text":1345},{"id":1460,"depth":230,"text":1461},{"id":1515,"depth":230,"text":1516},{"id":1645,"depth":230,"text":1646},{"id":1652,"depth":230,"text":1653},{"id":1690,"depth":230,"text":1691},"FastAPI vs Django REST Framework for commercial APIs: async throughput, the ORM reality, cost per million requests, failure modes and a safe migration path.","md",{"pageTitle":1754,"type":1755,"datePublished":1756,"dateModified":1757},"FastAPI vs Django REST Framework: Which Should You Ship?","article","2026-06-18","2026-07-23","\u002Fgetting-started-with-python-apis-for-builders\u002Fsetting-up-fastapi\u002Ffastapi-vs-django-rest-framework",{"title":5,"description":1751},"getting-started-with-python-apis-for-builders\u002Fsetting-up-fastapi\u002Ffastapi-vs-django-rest-framework\u002Findex","OLPu_Bvd8dCCIJSGsgdw6kffunlGr22kl7klNuDsBnU",{"@context":1763,"@type":1764,"mainEntity":1765},"https:\u002F\u002Fschema.org","FAQPage",[1766,1771,1774,1777,1780],{"@type":1767,"name":1658,"acceptedAnswer":1768},"Question",{"@type":1769,"text":1770},"Answer","On I\u002FO-heavy endpoints FastAPI wins by roughly five to one, because a synchronous worker sits idle during upstream waits while the event loop keeps serving. Ten million requests with a 150 ms vendor call need about six 4 vCPU boxes under DRF and one under FastAPI — call it $510 versus $85 a month. On plain database-bound CRUD the gap shrinks to about 20 percent and the choice stops being financial.",{"@type":1767,"name":1664,"acceptedAnswer":1772},{"@type":1769,"text":1773},"Almost never as a full rewrite. Move only the endpoints whose latency profile is dominated by waiting, run both services against the same database, and keep Django as the owner of migrations and the admin. A rewrite of stable CRUD burns weeks and returns a rounding error on your infrastructure bill.",{"@type":1767,"name":1670,"acceptedAnswer":1775},{"@type":1769,"text":1776},"Partially. Django supports async views, and DRF has been adding async support incrementally, but the ORM still executes queries in a thread executor and much of the third-party ecosystem assumes a synchronous call stack. Treat async DRF as a way to await outbound HTTP calls, not as an equal to a fully async stack.",{"@type":1767,"name":1676,"acceptedAnswer":1778},{"@type":1769,"text":1779},"DRF is faster for the first month when your product needs an admin, user accounts, and permissions — that is a fortnight of unbilled work you skip. FastAPI is faster from month two onward on an API-first product, because typed contracts and generated docs remove the SDK and documentation drift that otherwise eats a day per release.",{"@type":1767,"name":1682,"acceptedAnswer":1781},{"@type":1769,"text":1782},"DRF hands you token auth, permission classes, and throttling as configuration, which is quicker to stand up. FastAPI makes you assemble them, but the dependency system keeps the checks explicit and testable per route. Either way the design questions are the same ones covered in handling API authentication in Python.",1784887028659]