{"id":203,"date":"2026-10-15T18:55:30","date_gmt":"2026-10-15T18:55:30","guid":{"rendered":"https:\/\/devdojo.co.in\/?p=203"},"modified":"2026-10-15T18:55:30","modified_gmt":"2026-10-15T18:55:30","slug":"fastapi-tutorial","status":"publish","type":"post","link":"https:\/\/devdojo.co.in\/index.php\/2026\/10\/15\/fastapi-tutorial\/","title":{"rendered":"FastAPI Tutorial: Build a REST API in Python Step by Step"},"content":{"rendered":"\n<div class=\"dd-post\">\n<style>\n.single article:has(.dd-hero) .entry-media{display:none}\n.dd-post{--navy:#0b1020;--blue:#3178c6;--violet:#7c5cff;--ink:#1f2433;--muted:#5b6275;--line:#e3e6ef;font-size:17px;line-height:1.75;color:var(--ink)}\n.dd-post h2{margin:2.2em 0 .6em;font-size:1.6em;line-height:1.3;scroll-margin-top:90px}\n.dd-post h3{margin:1.6em 0 .5em;font-size:1.22em;scroll-margin-top:90px}\n.dd-post a{color:var(--blue);text-decoration:underline;text-underline-offset:3px}\n.dd-post pre{background:#0f1430;color:#e6e9f5;padding:18px 20px;border-radius:12px;overflow-x:auto;line-height:1.6;border:1px solid #232a52;margin:1.2em 0;font-size:14.5px !important}\n.dd-post pre code{font-size:1em !important;background:none;color:inherit;padding:0;font-family:\"JetBrains Mono\",\"Fira Code\",Consolas,Menlo,monospace;white-space:pre}\n.dd-post :not(pre)>code{background:#eef1fb;color:#2b3a8f;padding:2px 6px;border-radius:5px;font-size:.9em}\n.dd-post .dd-file{display:inline-block;background:#151b3b;color:#c9d1ff;font:600 13px\/1 \"JetBrains Mono\",Consolas,monospace;padding:7px 12px;border-radius:8px 8px 0 0;margin:1.2em 0 0}\n.dd-post .dd-file+pre{margin-top:0;border-top-left-radius:0}\n.dd-post .dd-hero{margin:0 0 1.6em;border-radius:16px;overflow:hidden;box-shadow:0 10px 30px rgba(11,16,32,.25)}\n.dd-post .dd-hero svg{display:block;width:100%;height:auto}\n.dd-post .dd-hero .node{transition:transform .25s,filter .25s;transform-box:fill-box;transform-origin:center;cursor:pointer}\n.dd-post .dd-hero .node:hover{transform:scale(1.08);filter:drop-shadow(0 0 10px #7c5cff)}\n.dd-post .dd-summary{background:linear-gradient(135deg,#eef3ff,#f4efff);border:1px solid #d6dcf5;border-left:5px solid var(--violet);border-radius:12px;padding:18px 22px;margin:1.4em 0}\n.dd-post .dd-summary strong.lbl{display:block;font-size:1.05em;margin-bottom:6px;color:#3a2fa0}\n.dd-post .dd-summary ul{margin:.3em 0 0 1.1em}\n.dd-post .dd-toc{background:#f8f9fc;border:1px solid var(--line);border-radius:12px;padding:16px 22px;margin:1.4em 0}\n.dd-post .dd-toc ol{margin:.4em 0 0 1.2em;columns:2;column-gap:28px}\n.dd-post .dd-toc li{break-inside:avoid;margin:.15em 0}\n@media(max-width:640px){.dd-post .dd-toc ol{columns:1}}\n.dd-post .dd-tip,.dd-post .dd-note,.dd-post .dd-warn{border-radius:10px;padding:14px 18px;margin:1.3em 0}\n.dd-post .dd-tip{background:#ecfbf3;border-left:4px solid #1f9d61}\n.dd-post .dd-note{background:#eef4ff;border-left:4px solid var(--blue)}\n.dd-post .dd-warn{background:#fff6e8;border-left:4px solid #e08a00}\n.dd-post .dd-cards{display:grid;grid-template-columns:repeat(auto-fit,minmax(210px,1fr));gap:14px;margin:1.3em 0}\n.dd-post .dd-card{border:1px solid var(--line);border-radius:12px;padding:14px 16px;background:#fff;box-shadow:0 2px 8px rgba(20,30,70,.05)}\n.dd-post .dd-card b{display:block;color:#3a2fa0;margin-bottom:4px}\n.dd-post table{width:100%;border-collapse:collapse;margin:1.3em 0;font-size:.95em}\n.dd-post th,.dd-post td{border:1px solid var(--line);padding:10px 12px;text-align:left;vertical-align:top}\n.dd-post th{background:#151b3b;color:#fff}\n.dd-post tr:nth-child(even) td{background:#f8f9fc}\n.dd-post .dd-tablewrap{overflow-x:auto}\n.dd-post figure{margin:1.6em 0;text-align:center}\n.dd-post figure svg{width:100%;height:auto;border-radius:12px}\n.dd-post figcaption{font-size:.9em;color:var(--muted);margin-top:8px}\n.dd-post details{border:1px solid var(--line);border-radius:10px;padding:12px 16px;margin:.7em 0;background:#fff}\n.dd-post details summary{cursor:pointer;font-weight:600}\n.dd-post details[open] summary{margin-bottom:8px;color:#3a2fa0}\n.dd-post .dd-tabs{border:1px solid var(--line);border-radius:12px;padding:6px 14px 16px;margin:1.4em 0;background:#fbfbfe}\n.dd-post .dd-tabs input{position:absolute;opacity:0;pointer-events:none}\n.dd-post .dd-tabs label{display:inline-block;padding:8px 14px;margin:8px 6px 0 0;border-radius:999px;border:1px solid #cfd5ea;cursor:pointer;font-weight:700;font-size:.92em;background:#fff;font-family:\"JetBrains Mono\",Consolas,monospace}\n.dd-post .dd-tabs .panel{display:none;margin-top:12px}\n.dd-post .dd-tabs .panel pre{margin:.6em 0}\n.dd-post .dd-tabs #mt1:checked~.labels label[for=mt1],.dd-post .dd-tabs #mt2:checked~.labels label[for=mt2],.dd-post .dd-tabs #mt3:checked~.labels label[for=mt3],.dd-post .dd-tabs #mt4:checked~.labels label[for=mt4],.dd-post .dd-tabs #mt5:checked~.labels label[for=mt5]{background:var(--violet);color:#fff;border-color:var(--violet)}\n.dd-post .dd-tabs #mt1:checked~.p1,.dd-post .dd-tabs #mt2:checked~.p2,.dd-post .dd-tabs #mt3:checked~.p3,.dd-post .dd-tabs #mt4:checked~.p4,.dd-post .dd-tabs #mt5:checked~.p5{display:block}\n.dd-post .dd-tabs .meta{font-size:.92em;color:var(--muted)}\n.dd-post .dd-tabs .meta b{color:var(--ink)}\n.dd-post .dd-cta{background:linear-gradient(135deg,#0b1020,#151b3b);color:#e8ebff;border-radius:16px;padding:24px 26px;margin:2em 0;text-align:center}\n.dd-post .dd-cta h3{color:#fff;margin-top:0}\n.dd-post .dd-cta a{color:#a99bff}\n<\/style>\n<div class=\"dd-hero\"><svg viewBox=\"0 0 1200 520\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" role=\"img\" aria-labelledby=\"ddh-t ddh-d\">\n<title id=\"ddh-t\">FastAPI Tutorial: Build a REST API in Python<\/title>\n<desc id=\"ddh-d\">Animated banner: requests travel from a client through FastAPI validation into a database and back as JSON.<\/desc>\n<defs>\n<linearGradient id=\"ddh-bg\" x1=\"0\" y1=\"0\" x2=\"1\" y2=\"1\"><stop offset=\"0\" stop-color=\"#0b1020\"\/><stop offset=\"1\" stop-color=\"#151b3b\"\/><\/linearGradient>\n<linearGradient id=\"ddh-ac\" x1=\"0\" y1=\"0\" x2=\"1\" y2=\"0\"><stop offset=\"0\" stop-color=\"#3178c6\"\/><stop offset=\"1\" stop-color=\"#7c5cff\"\/><\/linearGradient>\n<radialGradient id=\"ddh-glow\" cx=\".5\" cy=\".5\" r=\".5\"><stop offset=\"0\" stop-color=\"#7c5cff\" stop-opacity=\".35\"\/><stop offset=\"1\" stop-color=\"#7c5cff\" stop-opacity=\"0\"\/><\/radialGradient>\n<\/defs>\n<rect x=\"0\" y=\"-60\" width=\"1200\" height=\"640\" fill=\"url(#ddh-bg)\"\/>\n<circle cx=\"900\" cy=\"270\" r=\"270\" fill=\"url(#ddh-glow)\"\/>\n<circle cx=\"120\" cy=\"480\" r=\"170\" fill=\"url(#ddh-glow)\"\/>\n<g fill=\"#7c5cff\" opacity=\".25\"><circle cx=\"80\" cy=\"60\" r=\"2\"\/><circle cx=\"300\" cy=\"40\" r=\"2\"\/><circle cx=\"560\" cy=\"30\" r=\"2\"\/><circle cx=\"1150\" cy=\"40\" r=\"2\"\/><circle cx=\"1140\" cy=\"490\" r=\"2\"\/><circle cx=\"480\" cy=\"500\" r=\"2\"\/><\/g>\n<text x=\"60\" y=\"120\" fill=\"#ffffff\" font-family=\"Segoe UI,Roboto,Arial,sans-serif\" font-size=\"56\" font-weight=\"800\">FastAPI Tutorial<\/text>\n<rect x=\"60\" y=\"146\" width=\"220\" height=\"5\" rx=\"2.5\" fill=\"url(#ddh-ac)\"\/>\n<text x=\"60\" y=\"198\" fill=\"#c9d1ff\" font-family=\"Segoe UI,Roboto,Arial,sans-serif\" font-size=\"27\" font-weight=\"600\">Build a REST API in Python,<\/text>\n<text x=\"60\" y=\"236\" fill=\"#c9d1ff\" font-family=\"Segoe UI,Roboto,Arial,sans-serif\" font-size=\"27\" font-weight=\"600\">step by step<\/text>\n<g font-family=\"JetBrains Mono,Consolas,monospace\" font-size=\"17\" fill=\"#8f99c9\">\n<text x=\"60\" y=\"320\"><tspan fill=\"#a99bff\">@app.post<\/tspan>(&#8220;\/books&#8221;, status_code=201)<\/text>\n<text x=\"60\" y=\"350\"><tspan fill=\"#7fb6ff\">def<\/tspan> create_book(book: BookCreate,<\/text>\n<text x=\"223\" y=\"380\">session: SessionDep):<\/text>\n<text x=\"101\" y=\"410\"><tspan fill=\"#7fb6ff\">return<\/tspan> save(book)<\/text>\n<text x=\"60\" y=\"460\" fill=\"#5f6aa6\">$ fastapi dev main.py<\/text>\n<\/g>\n<g fill=\"none\" stroke=\"#3a4486\" stroke-width=\"2.5\" stroke-dasharray=\"7 7\">\n<path id=\"ddh-p1\" d=\"M770 150 L900 210\"\/>\n<path id=\"ddh-p2\" d=\"M900 310 L900 380\"\/>\n<path id=\"ddh-p3\" d=\"M1030 150 L900 210\"\/>\n<\/g>\n<g font-family=\"Segoe UI,Roboto,Arial,sans-serif\" text-anchor=\"middle\">\n<g class=\"node\"><rect x=\"680\" y=\"96\" width=\"180\" height=\"58\" rx=\"12\" fill=\"#1b2350\" stroke=\"#3178c6\" stroke-width=\"2.5\"\/><text x=\"770\" y=\"132\" fill=\"#e6e9ff\" font-size=\"19\" font-weight=\"600\">Client \/ React<\/text><\/g>\n<g class=\"node\"><rect x=\"950\" y=\"96\" width=\"170\" height=\"58\" rx=\"12\" fill=\"#1b2350\" stroke=\"#7c5cff\" stroke-width=\"2.5\"\/><text x=\"1035\" y=\"132\" fill=\"#e6e9ff\" font-size=\"19\" font-weight=\"600\">\/docs UI<\/text><\/g>\n<g class=\"node\"><rect x=\"800\" y=\"210\" width=\"200\" height=\"100\" rx=\"16\" fill=\"url(#ddh-ac)\"\/><text x=\"900\" y=\"252\" fill=\"#fff\" font-size=\"26\" font-weight=\"800\">\u26a1 FastAPI<\/text><text x=\"900\" y=\"284\" fill=\"#e9e6ff\" font-size=\"16\">validate \u00b7 route \u00b7 respond<\/text><\/g>\n<g class=\"node\"><rect x=\"800\" y=\"384\" width=\"200\" height=\"70\" rx=\"14\" fill=\"#1b2350\" stroke=\"#3178c6\" stroke-width=\"2.5\"\/><text x=\"900\" y=\"414\" fill=\"#e6e9ff\" font-size=\"19\" font-weight=\"600\">SQLModel<\/text><text x=\"900\" y=\"438\" fill=\"#8f99c9\" font-size=\"15\">SQLite database<\/text><\/g>\n<g class=\"node\"><rect x=\"1030\" y=\"250\" width=\"130\" height=\"50\" rx=\"10\" fill=\"#14361f\" stroke=\"#2fbf71\" stroke-width=\"2\"\/><text x=\"1095\" y=\"281\" fill=\"#9ff0c2\" font-size=\"17\" font-weight=\"700\">201 JSON<\/text><\/g>\n<g class=\"node\"><rect x=\"640\" y=\"250\" width=\"130\" height=\"50\" rx=\"10\" fill=\"#3a1820\" stroke=\"#ff6b81\" stroke-width=\"2\"\/><text x=\"705\" y=\"281\" fill=\"#ffb3c0\" font-size=\"17\" font-weight=\"700\">422 error<\/text><\/g>\n<\/g>\n<g stroke=\"#3a4486\" stroke-width=\"2\" stroke-dasharray=\"5 6\"><path d=\"M1000 275 L1030 275\"\/><path d=\"M800 275 L770 275\"\/><\/g>\n<circle r=\"6\" fill=\"#7c5cff\"><animateMotion dur=\"2.2s\" repeatCount=\"indefinite\"><mpath href=\"#ddh-p1\"\/><\/animateMotion><\/circle>\n<circle r=\"6\" fill=\"#3178c6\"><animateMotion dur=\"1.8s\" repeatCount=\"indefinite\"><mpath href=\"#ddh-p2\"\/><\/animateMotion><\/circle>\n<circle r=\"6\" fill=\"#a99bff\"><animateMotion dur=\"2.6s\" repeatCount=\"indefinite\" begin=\"0.6s\"><mpath href=\"#ddh-p3\"\/><\/animateMotion><\/circle>\n<\/svg><\/div>\n<p>If you know a little Python and want to build your own backend, this <strong>FastAPI tutorial<\/strong> is the fastest way to get there. In about 30 minutes you will build a real REST API for a small bookstore \u2014 create, read, update and delete books \u2014 with automatic data validation, a SQLite database, interactive documentation and tests. Every code example here was run against the latest releases (FastAPI 0.143, Pydantic 2.14, SQLModel 0.0.48) before publishing, so you can copy, paste and run.<\/p>\n\n<div class=\"dd-summary\">\n<strong class=\"lbl\">Quick Summary<\/strong>\n<ul>\n<li><strong>FastAPI<\/strong> is a modern Python web framework for building APIs. You write normal Python functions with type hints, and FastAPI turns them into validated, documented API endpoints.<\/li>\n<li>Install with <code>pip install \"fastapi[standard]\"<\/code> and start a live-reloading server with <code>fastapi dev main.py<\/code>.<\/li>\n<li><strong>Pydantic<\/strong> models check incoming data for you. Bad input automatically gets a clear <code>422<\/code> error.<\/li>\n<li><strong>SQLModel<\/strong> lets one class work as both a database table and an API model.<\/li>\n<li>Free interactive docs are available at <code>\/docs<\/code> \u2014 no extra setup.<\/li>\n<li>Test everything with <code>TestClient<\/code> and <code>pytest<\/code>, and run in production with <code>fastapi run<\/code>.<\/li>\n<\/ul>\n<\/div>\n\n<nav class=\"dd-toc\" aria-label=\"Table of contents\">\n<strong>Table of Contents<\/strong>\n<ol>\n<li><a href=\"#what-is-fastapi\">What is FastAPI and why use it?<\/a><\/li>\n<li><a href=\"#setup\">Step 1: Install FastAPI<\/a><\/li>\n<li><a href=\"#first-api\">Step 2: Your first FastAPI app<\/a><\/li>\n<li><a href=\"#how-it-works\">How a request flows through FastAPI<\/a><\/li>\n<li><a href=\"#validation\">Step 3: Validate data with Pydantic<\/a><\/li>\n<li><a href=\"#database\">Step 4: Add a database with SQLModel<\/a><\/li>\n<li><a href=\"#crud\">Step 5: Build the full CRUD API<\/a><\/li>\n<li><a href=\"#try-it\">Try every endpoint (interactive)<\/a><\/li>\n<li><a href=\"#testing\">Step 6: Test your API with pytest<\/a><\/li>\n<li><a href=\"#production\">Step 7: Run it in production<\/a><\/li>\n<li><a href=\"#errors\">Common errors and fixes<\/a><\/li>\n<li><a href=\"#best-practices\">Best practices<\/a><\/li>\n<li><a href=\"#faq\">FAQ<\/a><\/li>\n<li><a href=\"#conclusion\">Conclusion<\/a><\/li>\n<\/ol>\n<\/nav>\n<\/div>\n\n\n\n<div class=\"dd-post\">\n<h2 id=\"what-is-fastapi\">What is FastAPI and why use it?<\/h2>\n<p>FastAPI is an open-source Python framework for building web APIs \u2014 the part of an app that sends and receives JSON data. Your React app, mobile app or another service calls these APIs to save and load data. FastAPI was created by Sebasti\u00e1n Ram\u00edrez and is now one of the most popular Python frameworks on GitHub, used by companies like Microsoft, Uber and Netflix, and by countless startups in India and around the world.<\/p>\n<p>The big idea is simple: <strong>you describe your data with Python type hints, and FastAPI does the boring work for you<\/strong>. It reads the incoming request, converts and checks the data, calls your function, and turns the result back into JSON.<\/p>\n\n<div class=\"dd-cards\">\n<div class=\"dd-card\"><b>\u26a1 Fast to run<\/b>Built on Starlette and Uvicorn with async support, so it handles many requests at once.<\/div>\n<div class=\"dd-card\"><b>\u270d\ufe0f Fast to write<\/b>Less code than older frameworks. Type hints replace hand-written parsing and checks.<\/div>\n<div class=\"dd-card\"><b>\u2705 Automatic validation<\/b>Wrong data types or missing fields get a clear error message \u2014 no extra code.<\/div>\n<div class=\"dd-card\"><b>\ud83d\udcd8 Free docs<\/b>Interactive Swagger UI at <code>\/docs<\/code>, generated from your code.<\/div>\n<\/div>\n\n<div class=\"dd-note\"><strong>Who is this for?<\/strong> You should know basic Python: functions, classes and <code>pip<\/code>. You do not need to know Django, Flask or databases. If you want a refresher on what is new in Python itself, read our guide on <a href=\"https:\/\/devdojo.co.in\/index.php\/2026\/10\/05\/python-3-15-new-features\/\">Python 3.15 new features<\/a>.<\/div>\n\n<h2 id=\"setup\">Step 1: Install FastAPI<\/h2>\n<p>FastAPI needs Python 3.10 or newer (check with <code>python --version<\/code>). Always create a <strong>virtual environment<\/strong> \u2014 a private folder of packages for this one project \u2014 so it does not clash with other projects.<\/p>\n<div class=\"dd-file\">Terminal<\/div>\n<pre><code># 1. Create a project folder and a virtual environment\nmkdir books-api &amp;&amp; cd books-api\npython -m venv .venv\n\n# 2. Activate it\nsource .venv\/bin\/activate        # macOS \/ Linux\n# .venv\\Scripts\\activate         # Windows (PowerShell or CMD)\n\n# 3. Install FastAPI with its standard extras + SQLModel\npip install \"fastapi[standard]\" sqlmodel<\/code><\/pre>\n<p>The <code>[standard]<\/code> part installs useful extras: the <code>fastapi<\/code> command-line tool, the Uvicorn server, and helpers for forms and email validation. Without it you only get the core library.<\/p>\n<\/div>\n\n\n\n<div class=\"dd-post\">\n<h2 id=\"first-api\">Step 2: Your first FastAPI app<\/h2>\n<p>Create a file called <code>hello.py<\/code> with two endpoints (an <em>endpoint<\/em> is one URL + one HTTP method that your API answers):<\/p>\n<div class=\"dd-file\">hello.py<\/div>\n<pre><code>from fastapi import FastAPI\n\napp = FastAPI(title=\"DevDojo Books API\")\n\n\n@app.get(\"\/\")\ndef home():\n    return {\"message\": \"Hello from FastAPI!\"}\n\n\n@app.get(\"\/books\/{book_id}\")\ndef read_book(book_id: int, show_price: bool = False):\n    return {\"book_id\": book_id, \"show_price\": show_price}<\/code><\/pre>\n<p>Start the development server:<\/p>\n<div class=\"dd-file\">Terminal<\/div>\n<pre><code>fastapi dev hello.py<\/code><\/pre>\n<p><code>fastapi dev<\/code> starts the server at <code>http:\/\/127.0.0.1:8000<\/code> and <strong>reloads automatically<\/strong> whenever you save a file. Now open these in your browser:<\/p>\n<ul>\n<li><code>http:\/\/127.0.0.1:8000\/<\/code> \u2192 <code>{\"message\": \"Hello from FastAPI!\"}<\/code><\/li>\n<li><code>http:\/\/127.0.0.1:8000\/books\/7?show_price=true<\/code> \u2192 <code>{\"book_id\": 7, \"show_price\": true}<\/code><\/li>\n<li><code>http:\/\/127.0.0.1:8000\/books\/abc<\/code> \u2192 a <code>422<\/code> error, because <code>abc<\/code> is not an integer<\/li>\n<li><code>http:\/\/127.0.0.1:8000\/docs<\/code> \u2192 interactive documentation where you can click <strong>\u201cTry it out\u201d<\/strong><\/li>\n<\/ul>\n<p>Notice what happened. <code>book_id<\/code> appears in the URL path, so FastAPI treats it as a <strong>path parameter<\/strong>. <code>show_price<\/code> is not in the path, so it becomes a <strong>query parameter<\/strong> (the part after <code>?<\/code>). Because you wrote <code>int<\/code> and <code>bool<\/code>, FastAPI converts the text from the URL into real Python values \u2014 <code>\"true\"<\/code>, <code>\"1\"<\/code> and <code>\"yes\"<\/code> all become <code>True<\/code>.<\/p>\n<div class=\"dd-tip\"><strong>Tip:<\/strong> If you name your file <code>main.py<\/code> (or <code>app.py<\/code>), you can simply run <code>fastapi dev<\/code> with no file name \u2014 the CLI finds it automatically.<\/div>\n\n<h2 id=\"how-it-works\">How a request flows through FastAPI<\/h2>\n<p>Before we add a database, it helps to picture what happens on every request. The diagram below follows one <code>POST \/books<\/code> call from the client to the database and back.<\/p>\n<figure>\n<svg viewBox=\"0 0 1000 330\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" role=\"img\" aria-labelledby=\"f1-t f1-d\">\n<title id=\"f1-t\">How a FastAPI request flows<\/title>\n<desc id=\"f1-d\">Diagram: client request goes to Uvicorn, then FastAPI routing, Pydantic validation and dependencies, then your function and the database, and the response returns as JSON. Invalid data returns a 422 error early.<\/desc>\n<defs><marker id=\"f1-a\" viewBox=\"0 0 10 10\" refX=\"9\" refY=\"5\" markerWidth=\"7\" markerHeight=\"7\" orient=\"auto\"><path d=\"M0 0L10 5L0 10z\" fill=\"#7c5cff\"\/><\/marker><\/defs>\n<rect width=\"1000\" height=\"330\" rx=\"14\" fill=\"#0f1430\"\/>\n<g font-family=\"Segoe UI,Roboto,Arial,sans-serif\" text-anchor=\"middle\">\n<g><rect x=\"20\" y=\"70\" width=\"140\" height=\"80\" rx=\"12\" fill=\"#1b2350\" stroke=\"#3178c6\" stroke-width=\"2\"\/><text x=\"90\" y=\"105\" fill=\"#fff\" font-size=\"17\" font-weight=\"700\">Client<\/text><text x=\"90\" y=\"128\" fill=\"#8f99c9\" font-size=\"13\">POST \/books<\/text><\/g>\n<g><rect x=\"200\" y=\"70\" width=\"140\" height=\"80\" rx=\"12\" fill=\"#1b2350\" stroke=\"#3178c6\" stroke-width=\"2\"\/><text x=\"270\" y=\"105\" fill=\"#fff\" font-size=\"17\" font-weight=\"700\">Uvicorn<\/text><text x=\"270\" y=\"128\" fill=\"#8f99c9\" font-size=\"13\">web server<\/text><\/g>\n<g><rect x=\"380\" y=\"70\" width=\"140\" height=\"80\" rx=\"12\" fill=\"#1b2350\" stroke=\"#7c5cff\" stroke-width=\"2\"\/><text x=\"450\" y=\"105\" fill=\"#fff\" font-size=\"17\" font-weight=\"700\">Routing<\/text><text x=\"450\" y=\"128\" fill=\"#8f99c9\" font-size=\"13\">match URL + method<\/text><\/g>\n<g><rect x=\"560\" y=\"60\" width=\"180\" height=\"100\" rx=\"12\" fill=\"#2a2370\" stroke=\"#7c5cff\" stroke-width=\"2\"\/><text x=\"650\" y=\"95\" fill=\"#fff\" font-size=\"17\" font-weight=\"700\">Validation<\/text><text x=\"650\" y=\"117\" fill=\"#c9d1ff\" font-size=\"13\">Pydantic checks body<\/text><text x=\"650\" y=\"137\" fill=\"#c9d1ff\" font-size=\"13\">Depends() opens session<\/text><\/g>\n<g><rect x=\"780\" y=\"70\" width=\"190\" height=\"80\" rx=\"12\" fill=\"url(#f1-g)\" stroke=\"#3178c6\" stroke-width=\"2\"\/><text x=\"875\" y=\"105\" fill=\"#fff\" font-size=\"17\" font-weight=\"700\">Your function<\/text><text x=\"875\" y=\"128\" fill=\"#c9d1ff\" font-size=\"13\">create_book()<\/text><\/g>\n<g><rect x=\"790\" y=\"220\" width=\"170\" height=\"70\" rx=\"12\" fill=\"#1b2350\" stroke=\"#3178c6\" stroke-width=\"2\"\/><text x=\"875\" y=\"250\" fill=\"#fff\" font-size=\"16\" font-weight=\"700\">SQLite (books.db)<\/text><text x=\"875\" y=\"272\" fill=\"#8f99c9\" font-size=\"13\">via SQLModel<\/text><\/g>\n<g><rect x=\"380\" y=\"220\" width=\"320\" height=\"70\" rx=\"12\" fill=\"#14361f\" stroke=\"#2fbf71\" stroke-width=\"2\"\/><text x=\"540\" y=\"250\" fill=\"#9ff0c2\" font-size=\"16\" font-weight=\"700\">Response: 201 + JSON<\/text><text x=\"540\" y=\"272\" fill=\"#c6f5da\" font-size=\"13\">filtered by response_model=BookPublic<\/text><\/g>\n<g><rect x=\"20\" y=\"220\" width=\"300\" height=\"70\" rx=\"12\" fill=\"#3a1820\" stroke=\"#ff6b81\" stroke-width=\"2\"\/><text x=\"170\" y=\"250\" fill=\"#ffb3c0\" font-size=\"16\" font-weight=\"700\">Bad data? 422 error<\/text><text x=\"170\" y=\"272\" fill=\"#ffd6dd\" font-size=\"13\">your function never runs<\/text><\/g>\n<\/g>\n<defs><linearGradient id=\"f1-g\" x1=\"0\" y1=\"0\" x2=\"1\" y2=\"1\"><stop offset=\"0\" stop-color=\"#3178c6\"\/><stop offset=\"1\" stop-color=\"#7c5cff\"\/><\/linearGradient><\/defs>\n<g stroke=\"#7c5cff\" stroke-width=\"2.5\" fill=\"none\" marker-end=\"url(#f1-a)\">\n<path id=\"f1-p1\" d=\"M160 110 L196 110\"\/><path d=\"M340 110 L376 110\"\/><path d=\"M520 110 L556 110\"\/><path d=\"M740 110 L776 110\"\/>\n<path d=\"M875 150 L875 216\"\/><path d=\"M790 255 L704 255\"\/>\n<\/g>\n<path d=\"M600 160 C560 200 360 190 320 222\" stroke=\"#ff6b81\" stroke-width=\"2.5\" fill=\"none\" stroke-dasharray=\"6 6\" marker-end=\"url(#f1-a)\"\/>\n<path id=\"f1-run\" d=\"M90 110 L875 110 L875 255 L540 255\" fill=\"none\" stroke=\"none\"\/>\n<circle r=\"6\" fill=\"#a99bff\"><animateMotion dur=\"4s\" repeatCount=\"indefinite\"><mpath href=\"#f1-run\"\/><\/animateMotion><\/circle>\n<text x=\"500\" y=\"34\" fill=\"#c9d1ff\" font-family=\"Segoe UI,Roboto,Arial,sans-serif\" font-size=\"15\" text-anchor=\"middle\">Everything before \u201cYour function\u201d happens automatically<\/text>\n<\/svg>\n<figcaption>Figure 1: The life of one request. FastAPI validates data and prepares dependencies before calling your function; invalid data is rejected early with a 422 error.<\/figcaption>\n<\/figure>\n<p>The important part is the middle: <strong>validation and dependencies run before your code<\/strong>. By the time your function starts, you already have clean, typed data and an open database session. If validation fails, your function is never called at all.<\/p>\n<\/div>\n\n\n\n<div class=\"dd-post\">\n<h2 id=\"validation\">Step 3: Validate data with Pydantic<\/h2>\n<p>Real APIs receive JSON in the request <strong>body<\/strong> \u2014 for example, a form to add a new book. FastAPI uses <a href=\"https:\/\/docs.pydantic.dev\/latest\/\" target=\"_blank\" rel=\"noopener\">Pydantic<\/a> to describe what that JSON must look like. You write a class, and Pydantic checks every field.<\/p>\n<div class=\"dd-file\">schemas.py<\/div>\n<pre><code>from typing import Annotated\n\nfrom fastapi import FastAPI, Query\nfrom pydantic import BaseModel, Field\n\napp = FastAPI()\n\n\nclass BookIn(BaseModel):\n    title: str = Field(min_length=1, max_length=200)\n    author: str\n    price: float = Field(gt=0, description=\"Price in INR\")\n    in_stock: bool = True\n\n\n@app.post(\"\/books\")\ndef create_book(book: BookIn):\n    return {\"saved\": book}\n\n\n@app.get(\"\/search\")\ndef search_books(\n    q: Annotated[str, Query(min_length=2, max_length=50)],\n    limit: Annotated[int, Query(ge=1, le=100)] = 10,\n):\n    return {\"q\": q, \"limit\": limit}<\/code><\/pre>\n<p>A few things to notice:<\/p>\n<ul>\n<li><code>BookIn<\/code> inherits from <code>BaseModel<\/code>, so FastAPI reads it from the JSON body.<\/li>\n<li><code>Field(gt=0)<\/code> means \u201cgreater than 0\u201d. A negative price is rejected automatically.<\/li>\n<li><code>in_stock: bool = True<\/code> has a default, so it is optional in the request.<\/li>\n<li><code>Annotated[str, Query(min_length=2)]<\/code> adds rules to a query parameter. This <code>Annotated<\/code> style is the recommended way in current FastAPI.<\/li>\n<\/ul>\n<p>If someone sends <code>{\"title\": \"Bad\", \"author\": \"X\", \"price\": -5}<\/code>, they get this response with status <code>422 Unprocessable Content<\/code>:<\/p>\n<div class=\"dd-file\">Response<\/div>\n<pre><code>{\n  \"detail\": [\n    {\n      \"type\": \"greater_than\",\n      \"loc\": [\"body\", \"price\"],\n      \"msg\": \"Input should be greater than 0\",\n      \"input\": -5,\n      \"ctx\": {\"gt\": 0.0}\n    }\n  ]\n}<\/code><\/pre>\n<p>The <code>loc<\/code> field tells the caller exactly where the problem is (<code>body \u2192 price<\/code>), and <code>msg<\/code> explains it in plain English. Your frontend can show this message straight to the user.<\/p>\n\n<h2 id=\"database\">Step 4: Add a database with SQLModel<\/h2>\n<p>Data in a Python list disappears when the server restarts, so we need a database. We will use <strong>SQLite<\/strong> (a database stored in a single file, built into Python) and <strong>SQLModel<\/strong>, a library from the same author as FastAPI. SQLModel combines Pydantic (for validation) and SQLAlchemy (for databases), so one class can be both a table and an API model.<\/p>\n<p>A clean pattern is to create a few small models that share a common base. Each model has one clear job:<\/p>\n<figure>\n<svg viewBox=\"0 0 1000 360\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" role=\"img\" aria-labelledby=\"f2-t f2-d\">\n<title id=\"f2-t\">SQLModel model structure for the books API<\/title>\n<desc id=\"f2-d\">Diagram: BookBase holds title, author, price and in_stock. Book adds id and is the database table. BookCreate is used for input, BookPublic for output with id, and BookUpdate has all fields optional.<\/desc>\n<defs><marker id=\"f2-a\" viewBox=\"0 0 10 10\" refX=\"9\" refY=\"5\" markerWidth=\"7\" markerHeight=\"7\" orient=\"auto\"><path d=\"M0 0L10 5L0 10z\" fill=\"#7c5cff\"\/><\/marker><\/defs>\n<rect width=\"1000\" height=\"360\" rx=\"14\" fill=\"#0f1430\"\/>\n<g font-family=\"Segoe UI,Roboto,Arial,sans-serif\">\n<rect x=\"340\" y=\"24\" width=\"320\" height=\"120\" rx=\"12\" fill=\"#2a2370\" stroke=\"#7c5cff\" stroke-width=\"2\"\/>\n<text x=\"500\" y=\"56\" fill=\"#fff\" font-size=\"19\" font-weight=\"800\" text-anchor=\"middle\">BookBase (shared fields)<\/text>\n<g font-family=\"JetBrains Mono,Consolas,monospace\" font-size=\"14\" fill=\"#c9d1ff\"><text x=\"370\" y=\"86\">title: str<\/text><text x=\"500\" y=\"86\">author: str<\/text><text x=\"370\" y=\"110\">price: float (gt=0)<\/text><text x=\"370\" y=\"132\">in_stock: bool = True<\/text><\/g>\n<g stroke=\"#7c5cff\" stroke-width=\"2.2\" fill=\"none\" marker-end=\"url(#f2-a)\"><path d=\"M400 144 L130 210\"\/><path d=\"M460 144 L380 210\"\/><path d=\"M540 144 L620 210\"\/><\/g>\n<rect x=\"30\" y=\"214\" width=\"200\" height=\"120\" rx=\"12\" fill=\"#1b2350\" stroke=\"#3178c6\" stroke-width=\"2\"\/>\n<text x=\"130\" y=\"244\" fill=\"#fff\" font-size=\"17\" font-weight=\"700\" text-anchor=\"middle\">Book (table=True)<\/text><text x=\"130\" y=\"272\" fill=\"#a99bff\" font-family=\"JetBrains Mono,Consolas,monospace\" font-size=\"13\" text-anchor=\"middle\">+ id (primary key)<\/text><text x=\"130\" y=\"300\" fill=\"#8f99c9\" font-size=\"13\" text-anchor=\"middle\">the database table<\/text>\n<rect x=\"280\" y=\"214\" width=\"200\" height=\"120\" rx=\"12\" fill=\"#1b2350\" stroke=\"#3178c6\" stroke-width=\"2\"\/>\n<text x=\"380\" y=\"244\" fill=\"#fff\" font-size=\"17\" font-weight=\"700\" text-anchor=\"middle\">BookCreate<\/text><text x=\"380\" y=\"272\" fill=\"#a99bff\" font-family=\"JetBrains Mono,Consolas,monospace\" font-size=\"13\" text-anchor=\"middle\">no id<\/text><text x=\"380\" y=\"300\" fill=\"#8f99c9\" font-size=\"13\" text-anchor=\"middle\">what the client SENDS<\/text>\n<rect x=\"530\" y=\"214\" width=\"200\" height=\"120\" rx=\"12\" fill=\"#1b2350\" stroke=\"#2fbf71\" stroke-width=\"2\"\/>\n<text x=\"630\" y=\"244\" fill=\"#fff\" font-size=\"17\" font-weight=\"700\" text-anchor=\"middle\">BookPublic<\/text><text x=\"630\" y=\"272\" fill=\"#9ff0c2\" font-family=\"JetBrains Mono,Consolas,monospace\" font-size=\"13\" text-anchor=\"middle\">+ id: int (required)<\/text><text x=\"630\" y=\"300\" fill=\"#8f99c9\" font-size=\"13\" text-anchor=\"middle\">what the client GETS<\/text>\n<rect x=\"780\" y=\"214\" width=\"190\" height=\"120\" rx=\"12\" fill=\"#1b2350\" stroke=\"#e08a00\" stroke-width=\"2\"\/>\n<text x=\"875\" y=\"244\" fill=\"#fff\" font-size=\"17\" font-weight=\"700\" text-anchor=\"middle\">BookUpdate<\/text><text x=\"875\" y=\"272\" fill=\"#ffd38a\" font-family=\"JetBrains Mono,Consolas,monospace\" font-size=\"13\" text-anchor=\"middle\">all fields | None<\/text><text x=\"875\" y=\"300\" fill=\"#8f99c9\" font-size=\"13\" text-anchor=\"middle\">for PATCH requests<\/text>\n<text x=\"875\" y=\"60\" fill=\"#8f99c9\" font-size=\"14\" text-anchor=\"middle\">separate class,<\/text><text x=\"875\" y=\"80\" fill=\"#8f99c9\" font-size=\"14\" text-anchor=\"middle\">every field optional<\/text>\n<\/g>\n<circle r=\"5\" fill=\"#a99bff\"><animateMotion dur=\"2.4s\" repeatCount=\"indefinite\" path=\"M400 144 L130 210\"\/><\/circle>\n<circle r=\"5\" fill=\"#a99bff\"><animateMotion dur=\"2.4s\" begin=\".8s\" repeatCount=\"indefinite\" path=\"M460 144 L380 210\"\/><\/circle>\n<circle r=\"5\" fill=\"#9ff0c2\"><animateMotion dur=\"2.4s\" begin=\"1.6s\" repeatCount=\"indefinite\" path=\"M540 144 L620 210\"\/><\/circle>\n<\/svg>\n<figcaption>Figure 2: One shared base, small focused models. The table model stores data, while the create, public and update models control what goes in and out of the API.<\/figcaption>\n<\/figure>\n<p>Why not use just one class? Because the client should <strong>never<\/strong> send the <code>id<\/code> (the database creates it), but should <strong>always<\/strong> receive it back. And for updates, every field is optional. Separate models keep these rules clear and stop users from overwriting fields they should not touch.<\/p>\n<\/div>\n\n\n\n<div class=\"dd-post\">\n<h2 id=\"crud\">Step 5: Build the full CRUD API<\/h2>\n<p>CRUD means <strong>C<\/strong>reate, <strong>R<\/strong>ead, <strong>U<\/strong>pdate, <strong>D<\/strong>elete \u2014 the four basic things every data API does. Here is the complete, working app in a single file. Create <code>main.py<\/code>:<\/p>\n<div class=\"dd-file\">main.py<\/div>\n<pre><code>from contextlib import asynccontextmanager\nfrom typing import Annotated\n\nfrom fastapi import Depends, FastAPI, HTTPException, Query, status\nfrom sqlmodel import Field, Session, SQLModel, create_engine, select\n\n\n# ---------- Models ----------\nclass BookBase(SQLModel):\n    title: str = Field(min_length=1, max_length=200, index=True)\n    author: str = Field(min_length=1, max_length=100)\n    price: float = Field(gt=0)\n    in_stock: bool = True\n\n\nclass Book(BookBase, table=True):\n    id: int | None = Field(default=None, primary_key=True)\n\n\nclass BookCreate(BookBase):\n    pass\n\n\nclass BookPublic(BookBase):\n    id: int\n\n\nclass BookUpdate(SQLModel):\n    title: str | None = None\n    author: str | None = None\n    price: float | None = Field(default=None, gt=0)\n    in_stock: bool | None = None\n\n\n# ---------- Database ----------\nDATABASE_URL = \"sqlite:\/\/\/books.db\"\nengine = create_engine(DATABASE_URL, connect_args={\"check_same_thread\": False})\n\n\ndef get_session():\n    with Session(engine) as session:\n        yield session\n\n\nSessionDep = Annotated[Session, Depends(get_session)]\n\n\n@asynccontextmanager\nasync def lifespan(app: FastAPI):\n    SQLModel.metadata.create_all(engine)  # runs once at startup\n    yield\n\n\napp = FastAPI(title=\"DevDojo Books API\", lifespan=lifespan)\n\n\n# ---------- Routes ----------\n@app.post(\"\/books\", response_model=BookPublic, status_code=status.HTTP_201_CREATED)\ndef create_book(book: BookCreate, session: SessionDep):\n    db_book = Book.model_validate(book)\n    session.add(db_book)\n    session.commit()\n    session.refresh(db_book)\n    return db_book\n\n\n@app.get(\"\/books\", response_model=list[BookPublic])\ndef list_books(\n    session: SessionDep,\n    offset: int = 0,\n    limit: Annotated[int, Query(le=100)] = 20,\n):\n    return session.exec(select(Book).offset(offset).limit(limit)).all()\n\n\n@app.get(\"\/books\/{book_id}\", response_model=BookPublic)\ndef get_book(book_id: int, session: SessionDep):\n    book = session.get(Book, book_id)\n    if not book:\n        raise HTTPException(status_code=404, detail=\"Book not found\")\n    return book\n\n\n@app.patch(\"\/books\/{book_id}\", response_model=BookPublic)\ndef update_book(book_id: int, data: BookUpdate, session: SessionDep):\n    book = session.get(Book, book_id)\n    if not book:\n        raise HTTPException(status_code=404, detail=\"Book not found\")\n    book.sqlmodel_update(data.model_dump(exclude_unset=True))\n    session.add(book)\n    session.commit()\n    session.refresh(book)\n    return book\n\n\n@app.delete(\"\/books\/{book_id}\", status_code=status.HTTP_204_NO_CONTENT)\ndef delete_book(book_id: int, session: SessionDep):\n    book = session.get(Book, book_id)\n    if not book:\n        raise HTTPException(status_code=404, detail=\"Book not found\")\n    session.delete(book)\n    session.commit()<\/code><\/pre>\n<p>Run it with <code>fastapi dev main.py<\/code>. A <code>books.db<\/code> file appears in your folder on first start. Let us walk through the key ideas.<\/p>\n\n<h3>The lifespan function<\/h3>\n<p><code>lifespan<\/code> runs code once when the app starts (before <code>yield<\/code>) and once when it stops (after <code>yield<\/code>). We use it to create the tables. This replaces the older <code>@app.on_event(\"startup\")<\/code> style, which is deprecated.<\/p>\n\n<h3>Dependency injection with <code>Depends<\/code><\/h3>\n<p><code>get_session()<\/code> opens a database session, <em>gives<\/em> it to your route with <code>yield<\/code>, and closes it after the response \u2014 even if an error happens. <code>SessionDep<\/code> is just a short name for <code>Annotated[Session, Depends(get_session)]<\/code>, so every route that needs the database simply adds <code>session: SessionDep<\/code>. This is called <strong>dependency injection<\/strong>: FastAPI creates the things your function needs and passes them in.<\/p>\n\n<h3>Response models<\/h3>\n<p><code>response_model=BookPublic<\/code> tells FastAPI to filter and document the output. Even if the database object had private fields (like a password hash in a user table), only the fields in <code>BookPublic<\/code> are sent back. It also makes the <code>\/docs<\/code> page show the exact response shape.<\/p>\n\n<h3>Partial updates with PATCH<\/h3>\n<p><code>data.model_dump(exclude_unset=True)<\/code> returns <strong>only the fields the client actually sent<\/strong>. So <code>{\"price\": 349}<\/code> changes the price and leaves the title, author and stock alone. Then <code>sqlmodel_update()<\/code> copies those values onto the database object.<\/p>\n\n<h3>Errors and status codes<\/h3>\n<p><code>raise HTTPException(status_code=404, ...)<\/code> stops the function and sends a clean JSON error. We also set <code>201 Created<\/code> for new books and <code>204 No Content<\/code> for deletes \u2014 small details that make your API feel professional to other developers.<\/p>\n<div class=\"dd-warn\"><strong>Note on sync vs async:<\/strong> our routes use normal <code>def<\/code> because the SQLite driver is synchronous. FastAPI runs <code>def<\/code> routes in a thread pool, so they do not block the server. Use <code>async def<\/code> only when everything inside it is async (for example, an async database driver or <code>httpx.AsyncClient<\/code>). Calling slow blocking code inside <code>async def<\/code> freezes the whole server.<\/div>\n<\/div>\n\n\n\n<div class=\"dd-post\">\n<h2 id=\"try-it\">Try every endpoint (interactive)<\/h2>\n<p>Click a method below to see the request and the response you get from the API we just built. You can run the same commands in your terminal with <code>curl<\/code>, or click \u201cTry it out\u201d on <code>\/docs<\/code>.<\/p>\n<div class=\"dd-tabs\">\n<input type=\"radio\" name=\"dd-mt\" id=\"mt1\" checked><input type=\"radio\" name=\"dd-mt\" id=\"mt2\"><input type=\"radio\" name=\"dd-mt\" id=\"mt3\"><input type=\"radio\" name=\"dd-mt\" id=\"mt4\"><input type=\"radio\" name=\"dd-mt\" id=\"mt5\">\n<div class=\"labels\"><label for=\"mt1\">POST<\/label><label for=\"mt2\">GET list<\/label><label for=\"mt3\">GET one<\/label><label for=\"mt4\">PATCH<\/label><label for=\"mt5\">DELETE<\/label><\/div>\n<div class=\"panel p1\"><p class=\"meta\"><b>Create a book<\/b> \u00b7 <code>POST \/books<\/code> \u00b7 returns <b>201 Created<\/b><\/p>\n<pre><code>Request body:\n{\"title\": \"Atomic Habits\", \"author\": \"James Clear\", \"price\": 399}\n\nResponse:\n{\"title\": \"Atomic Habits\", \"author\": \"James Clear\", \"price\": 399.0, \"in_stock\": true, \"id\": 1}<\/code><\/pre><\/div>\n<div class=\"panel p2\"><p class=\"meta\"><b>List books with paging<\/b> \u00b7 <code>GET \/books?offset=0&amp;limit=20<\/code> \u00b7 returns <b>200 OK<\/b><\/p>\n<pre><code>Response:\n[\n  {\"title\": \"Atomic Habits\", \"author\": \"James Clear\", \"price\": 399.0, \"in_stock\": true, \"id\": 1}\n]\n\nlimit=500  \u2192  422 error (we set le=100 to protect the server)<\/code><\/pre><\/div>\n<div class=\"panel p3\"><p class=\"meta\"><b>Get one book<\/b> \u00b7 <code>GET \/books\/1<\/code> \u00b7 returns <b>200 OK<\/b> or <b>404<\/b><\/p>\n<pre><code>GET \/books\/1     \u2192 {\"title\": \"Atomic Habits\", ..., \"id\": 1}\nGET \/books\/9999  \u2192 404 {\"detail\": \"Book not found\"}\nGET \/books\/abc   \u2192 422 (book_id must be an integer)<\/code><\/pre><\/div>\n<div class=\"panel p4\"><p class=\"meta\"><b>Update only some fields<\/b> \u00b7 <code>PATCH \/books\/1<\/code> \u00b7 returns <b>200 OK<\/b><\/p>\n<pre><code>Request body:\n{\"price\": 349}\n\nResponse (title, author and in_stock unchanged):\n{\"title\": \"Atomic Habits\", \"author\": \"James Clear\", \"price\": 349.0, \"in_stock\": true, \"id\": 1}<\/code><\/pre><\/div>\n<div class=\"panel p5\"><p class=\"meta\"><b>Delete a book<\/b> \u00b7 <code>DELETE \/books\/1<\/code> \u00b7 returns <b>204 No Content<\/b><\/p>\n<pre><code>Response: (empty body, status 204)\n\nDeleting again \u2192 404 {\"detail\": \"Book not found\"}<\/code><\/pre><\/div>\n<\/div>\n<p>The same calls with <code>curl<\/code>:<\/p>\n<div class=\"dd-file\">Terminal<\/div>\n<pre><code># Create a book\ncurl -X POST http:\/\/127.0.0.1:8000\/books \\\n  -H \"Content-Type: application\/json\" \\\n  -d '{\"title\": \"Atomic Habits\", \"author\": \"James Clear\", \"price\": 399}'\n\n# List books (first 20)\ncurl \"http:\/\/127.0.0.1:8000\/books?limit=20\"\n\n# Update only the price\ncurl -X PATCH http:\/\/127.0.0.1:8000\/books\/1 \\\n  -H \"Content-Type: application\/json\" \\\n  -d '{\"price\": 349}'\n\n# Delete it\ncurl -X DELETE http:\/\/127.0.0.1:8000\/books\/1<\/code><\/pre>\n<div class=\"dd-tip\"><strong>Windows users:<\/strong> in PowerShell, <code>curl<\/code> may point to <code>Invoke-WebRequest<\/code>. Use <code>curl.exe<\/code> instead, or simply test from the <code>\/docs<\/code> page in your browser.<\/div>\n<\/div>\n\n\n\n<div class=\"dd-post\">\n<h2 id=\"testing\">Step 6: Test your API with pytest<\/h2>\n<p>Tests prove your API still works after every change. FastAPI includes <code>TestClient<\/code>, which calls your app directly in Python \u2014 no running server needed. The trick is <code>dependency_overrides<\/code>: we swap the real database session for one that uses a fresh in-memory database, so tests never touch <code>books.db<\/code>.<\/p>\n<div class=\"dd-file\">test_main.py<\/div>\n<pre><code>from fastapi.testclient import TestClient\nfrom sqlmodel import Session, SQLModel, create_engine\nfrom sqlmodel.pool import StaticPool\n\nfrom main import app, get_session\n\nengine = create_engine(\n    \"sqlite:\/\/\",  # in-memory database, fresh for tests\n    connect_args={\"check_same_thread\": False},\n    poolclass=StaticPool,\n)\nSQLModel.metadata.create_all(engine)\n\n\ndef get_test_session():\n    with Session(engine) as session:\n        yield session\n\n\napp.dependency_overrides[get_session] = get_test_session\nclient = TestClient(app)\n\n\ndef test_create_and_read_book():\n    res = client.post(\"\/books\", json={\"title\": \"Clean Code\", \"author\": \"Robert Martin\", \"price\": 499})\n    assert res.status_code == 201\n    book_id = res.json()[\"id\"]\n\n    res = client.get(f\"\/books\/{book_id}\")\n    assert res.status_code == 200\n    assert res.json()[\"title\"] == \"Clean Code\"\n\n\ndef test_invalid_price_returns_422():\n    res = client.post(\"\/books\", json={\"title\": \"Bad\", \"author\": \"X\", \"price\": -5})\n    assert res.status_code == 422\n\n\ndef test_missing_book_returns_404():\n    assert client.get(\"\/books\/9999\").status_code == 404<\/code><\/pre>\n<p>Run <code>pytest -q<\/code> and you should see <code>3 passed<\/code>. <code>StaticPool<\/code> makes every connection share the same in-memory database, which is required for SQLite <code>:memory:<\/code> databases in tests.<\/p>\n\n<h2 id=\"production\">Step 7: Run it in production<\/h2>\n<p><code>fastapi dev<\/code> is for your laptop only. For a server, use <code>fastapi run<\/code>, which turns off auto-reload and listens on all network interfaces (<code>0.0.0.0<\/code>):<\/p>\n<div class=\"dd-file\">Terminal<\/div>\n<pre><code># Production: no auto-reload, listens on 0.0.0.0, multiple worker processes\nfastapi run main.py --workers 4\n\n# Run the tests\npip install pytest\npytest -q<\/code><\/pre>\n<p>For real deployments you will usually put this inside a Docker container. Our <a href=\"https:\/\/devdojo.co.in\/index.php\/2026\/10\/10\/docker-compose-tutorial\/\">Docker Compose tutorial<\/a> shows how to run an API with PostgreSQL and Redis together \u2014 swap SQLite for PostgreSQL by changing <code>DATABASE_URL<\/code> to something like <code>postgresql+psycopg:\/\/user:pass@db:5432\/books<\/code> and installing <code>psycopg[binary]<\/code>.<\/p>\n<div class=\"dd-note\"><strong>Before going live:<\/strong> add <code>CORSMiddleware<\/code> if a browser frontend on another domain will call your API, keep secrets like database passwords in environment variables (not in code), and use a migration tool such as Alembic instead of <code>create_all()<\/code> once your tables change over time.<\/div>\n<\/div>\n\n\n\n<div class=\"dd-post\">\n<h2 id=\"errors\">Common errors and fixes<\/h2>\n<div class=\"dd-tablewrap\">\n<table>\n<thead><tr><th>Error \/ symptom<\/th><th>Why it happens<\/th><th>How to fix it<\/th><\/tr><\/thead>\n<tbody>\n<tr><td><code>fastapi: command not found<\/code><\/td><td>You installed plain <code>fastapi<\/code>, or your virtual environment is not active.<\/td><td>Activate <code>.venv<\/code> and run <code>pip install \"fastapi[standard]\"<\/code> (keep the quotes).<\/td><\/tr>\n<tr><td><code>422 Unprocessable Content<\/code><\/td><td>Request data does not match your model \u2014 wrong type, missing field or failed rule.<\/td><td>Read the <code>loc<\/code> and <code>msg<\/code> in the response. Check that you send JSON with the <code>Content-Type: application\/json<\/code> header.<\/td><\/tr>\n<tr><td><code>Error loading ASGI app. Could not import module \"main\"<\/code><\/td><td>Wrong file name or you are running the command from another folder.<\/td><td>Run it from the folder that contains the file, or pass the path: <code>fastapi dev app\/main.py<\/code>.<\/td><\/tr>\n<tr><td><code>Address already in use<\/code><\/td><td>Another server is still running on port 8000.<\/td><td>Stop it with Ctrl+C, or use another port: <code>fastapi dev main.py --port 8001<\/code>.<\/td><\/tr>\n<tr><td><code>SQLite objects created in a thread can only be used in that same thread<\/code><\/td><td>FastAPI runs <code>def<\/code> routes in different threads.<\/td><td>Pass <code>connect_args={\"check_same_thread\": False}<\/code> to <code>create_engine<\/code> (already in our code).<\/td><\/tr>\n<tr><td><code>no such table: book<\/code><\/td><td>Tables were never created.<\/td><td>Make sure <code>create_all()<\/code> runs in <code>lifespan<\/code> and that <code>lifespan=lifespan<\/code> is passed to <code>FastAPI()<\/code>.<\/td><\/tr>\n<tr><td>Browser shows a CORS error<\/td><td>Your frontend runs on a different origin (e.g. <code>localhost:3000<\/code>).<\/td><td>Add <code>CORSMiddleware<\/code> with <code>allow_origins=[\"http:\/\/localhost:3000\"]<\/code>.<\/td><\/tr>\n<tr><td>Deprecation warning about <code>httpx<\/code> when running tests<\/td><td>Recent Starlette versions moved <code>TestClient<\/code> to the newer <code>httpx2<\/code> package.<\/td><td>Run <code>pip install httpx2<\/code>. Your tests stay the same.<\/td><\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n\n<h2 id=\"best-practices\">Best practices<\/h2>\n<ul>\n<li><strong>Use separate models for input and output<\/strong> (<code>BookCreate<\/code>, <code>BookPublic<\/code>, <code>BookUpdate<\/code>). Never return a database model with secrets directly.<\/li>\n<li><strong>Prefer the <code>Annotated<\/code> style<\/strong> for <code>Query<\/code>, <code>Path<\/code> and <code>Depends<\/code>. It is reusable and the officially recommended approach.<\/li>\n<li><strong>Always limit list endpoints<\/strong> with <code>offset<\/code>\/<code>limit<\/code> and a maximum (<code>le=100<\/code>), so one request cannot load your whole table.<\/li>\n<li><strong>Return correct status codes<\/strong>: 201 for create, 204 for delete, 404 when not found, 422 for bad input.<\/li>\n<li><strong>Do not mix blocking code with <code>async def<\/code>.<\/strong> If unsure, use plain <code>def<\/code>.<\/li>\n<li><strong>Split big apps with <code>APIRouter<\/code><\/strong> \u2014 for example <code>routers\/books.py<\/code> and <code>routers\/users.py<\/code> \u2014 and include them with <code>app.include_router()<\/code>.<\/li>\n<li><strong>Read settings from environment variables<\/strong> (the <code>pydantic-settings<\/code> package works well) instead of hard-coding URLs and passwords.<\/li>\n<li><strong>Write tests from day one<\/strong> using <code>TestClient<\/code> and <code>dependency_overrides<\/code>.<\/li>\n<\/ul>\n<p>For deeper topics like security (OAuth2 and JWT), background tasks and WebSockets, the <a href=\"https:\/\/fastapi.tiangolo.com\/tutorial\/\" target=\"_blank\" rel=\"noopener\">official FastAPI tutorial<\/a> and the <a href=\"https:\/\/sqlmodel.tiangolo.com\/tutorial\/fastapi\/\" target=\"_blank\" rel=\"noopener\">SQLModel + FastAPI guide<\/a> are excellent next steps.<\/p>\n<\/div>\n\n\n\n<div class=\"dd-post\">\n<h2 id=\"faq\">FAQ<\/h2>\n<details><summary>Is FastAPI better than Flask or Django?<\/summary><p>It depends on the job. FastAPI is great for APIs: you get validation, async support and docs built in. Flask is smaller and very flexible but needs extra libraries for the same features. Django is a full framework with an admin panel, templates and an ORM \u2014 a good fit for content-heavy websites. For a new JSON API, many teams pick FastAPI.<\/p><\/details>\n<details><summary>Is FastAPI good for beginners?<\/summary><p>Yes. If you know Python functions and classes, you can build a working API in an afternoon. The type hints you write also act as documentation, and the <code>\/docs<\/code> page lets you test without any extra tools.<\/p><\/details>\n<details><summary>Do I have to use async def?<\/summary><p>No. Plain <code>def<\/code> works perfectly and FastAPI runs it in a thread pool. Use <code>async def<\/code> only when you call async libraries with <code>await<\/code>, such as async database drivers or <code>httpx.AsyncClient<\/code>.<\/p><\/details>\n<details><summary>Can I use PostgreSQL or MySQL instead of SQLite?<\/summary><p>Yes. SQLModel works with any database SQLAlchemy supports. Install the driver (for example <code>psycopg[binary]<\/code> for PostgreSQL), change the database URL, and remove the <code>check_same_thread<\/code> option, which is only for SQLite.<\/p><\/details>\n<details><summary>What is the difference between fastapi dev and fastapi run?<\/summary><p><code>fastapi dev<\/code> is for development: it auto-reloads on file changes and listens only on your own machine. <code>fastapi run<\/code> is for production: no reload, listens on all network interfaces, and supports <code>--workers<\/code> for multiple processes.<\/p><\/details>\n<details><summary>Is FastAPI used in real jobs in India?<\/summary><p>Yes. FastAPI is very common in Indian startups and product companies, especially for AI and machine-learning backends, because most AI libraries are in Python. Knowing FastAPI, Pydantic and SQL is a strong combination for Python backend roles.<\/p><\/details>\n\n<h2 id=\"conclusion\">Conclusion: What You Built in This FastAPI Tutorial<\/h2>\n<p>In this FastAPI tutorial you built a complete REST API: endpoints with path and query parameters, data validation with Pydantic, a SQLite database with SQLModel, clean CRUD routes, automatic docs, tests with pytest and a production command. That is the same foundation used by real-world Python backends.<\/p>\n<p>A good next step is to add a <code>User<\/code> model with login, or to move the database to PostgreSQL inside Docker. Try building one small feature on your own \u2014 like a \u201csearch by author\u201d endpoint \u2014 and you will remember these patterns much better.<\/p>\n\n<div class=\"dd-cta\">\n<h3>New posts every day on DevDojo<\/h3>\n<p>Practical, beginner-friendly tutorials on Python, JavaScript, AI, DevOps and interview prep. Bookmark <a href=\"https:\/\/devdojo.co.in\/\">devdojo.co.in<\/a> and come back tomorrow \u2014 and tell us in the comments what you built with FastAPI!<\/p>\n<\/div>\n<\/div>\n\n","protected":false},"excerpt":{"rendered":"<p>Learn FastAPI step by step: build a CRUD REST API in Python with Pydantic validation, SQLModel and SQLite, automatic docs, pytest tests and deployment tips.<\/p>\n","protected":false},"author":1,"featured_media":202,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[7,10,9],"tags":[101,97,100,52,98,99],"class_list":["post-203","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-backend","category-fastapi","category-python","tag-backend-tutorial","tag-fastapi","tag-pydantic","tag-python","tag-rest-api","tag-sqlmodel"],"aioseo_notices":[],"aioseo_head":"\n\t\t<!-- All in One SEO 5.0.2.1 - aioseo.com -->\n\t<meta name=\"description\" content=\"Learn FastAPI step by step: build a CRUD REST API in Python with Pydantic validation, SQLModel and SQLite, automatic docs, pytest tests and deployment tips.\" \/>\n\t<meta name=\"robots\" content=\"max-image-preview:large\" \/>\n\t<meta name=\"author\" content=\"Lokendra\"\/>\n\t<link rel=\"canonical\" href=\"https:\/\/devdojo.co.in\/index.php\/2026\/10\/15\/fastapi-tutorial\/\" \/>\n\t<meta name=\"generator\" content=\"All in One SEO (AIOSEO) 5.0.2.1\" \/>\n\t\t<meta property=\"og:locale\" content=\"en_US\" \/>\n\t\t<meta property=\"og:site_name\" content=\"DevDojo - Empowering Developers\" \/>\n\t\t<meta property=\"og:type\" content=\"article\" \/>\n\t\t<meta property=\"og:title\" content=\"FastAPI Tutorial: Build a REST API in Python Step by Step\" \/>\n\t\t<meta property=\"og:description\" content=\"Learn FastAPI step by step: build a CRUD REST API in Python with Pydantic validation, SQLModel and SQLite, automatic docs, pytest tests and deployment tips.\" \/>\n\t\t<meta property=\"og:url\" content=\"https:\/\/devdojo.co.in\/index.php\/2026\/10\/15\/fastapi-tutorial\/\" \/>\n\t\t<meta property=\"article:published_time\" content=\"2026-10-15T18:55:30+00:00\" \/>\n\t\t<meta property=\"article:modified_time\" content=\"2026-10-15T18:55:30+00:00\" \/>\n\t\t<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n\t\t<meta name=\"twitter:title\" content=\"FastAPI Tutorial: Build a REST API in Python Step by Step\" \/>\n\t\t<meta name=\"twitter:description\" content=\"Learn FastAPI step by step: build a CRUD REST API in Python with Pydantic validation, SQLModel and SQLite, automatic docs, pytest tests and deployment tips.\" \/>\n\t\t<script type=\"application\/ld+json\" class=\"aioseo-schema\">\n\t\t\t{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"BlogPosting\",\"@id\":\"https:\\\/\\\/devdojo.co.in\\\/index.php\\\/2026\\\/10\\\/15\\\/fastapi-tutorial\\\/#blogposting\",\"name\":\"FastAPI Tutorial: Build a REST API in Python Step by Step\",\"headline\":\"FastAPI Tutorial: Build a REST API in Python Step by Step\",\"author\":{\"@id\":\"https:\\\/\\\/devdojo.co.in\\\/index.php\\\/author\\\/lokendra\\\/#author\"},\"publisher\":{\"@id\":\"https:\\\/\\\/devdojo.co.in\\\/#organization\"},\"image\":{\"@type\":\"ImageObject\",\"url\":\"https:\\\/\\\/devdojo.co.in\\\/wp-content\\\/uploads\\\/2026\\\/10\\\/fastapi-tutorial.jpg\",\"width\":1200,\"height\":630,\"caption\":\"FastAPI tutorial banner: a request flows from a client through FastAPI validation to a SQLModel database and returns JSON\"},\"datePublished\":\"2026-10-15T18:55:30+00:00\",\"dateModified\":\"2026-10-15T18:55:30+00:00\",\"inLanguage\":\"en-US\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/devdojo.co.in\\\/index.php\\\/2026\\\/10\\\/15\\\/fastapi-tutorial\\\/#webpage\"},\"isPartOf\":{\"@id\":\"https:\\\/\\\/devdojo.co.in\\\/index.php\\\/2026\\\/10\\\/15\\\/fastapi-tutorial\\\/#webpage\"},\"articleSection\":\"Backend, FastAPI, Python, Backend Tutorial, FastAPI, Pydantic, Python, REST API, SQLModel\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/devdojo.co.in\\\/index.php\\\/2026\\\/10\\\/15\\\/fastapi-tutorial\\\/#breadcrumblist\",\"itemListElement\":[{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/devdojo.co.in\\\/#listItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/devdojo.co.in\\\/\",\"nextItem\":{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/devdojo.co.in\\\/index.php\\\/category\\\/backend\\\/#listItem\",\"name\":\"Backend\"}},{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/devdojo.co.in\\\/index.php\\\/category\\\/backend\\\/#listItem\",\"position\":2,\"name\":\"Backend\",\"item\":\"https:\\\/\\\/devdojo.co.in\\\/index.php\\\/category\\\/backend\\\/\",\"nextItem\":{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/devdojo.co.in\\\/index.php\\\/category\\\/backend\\\/python\\\/#listItem\",\"name\":\"Python\"},\"previousItem\":{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/devdojo.co.in\\\/#listItem\",\"name\":\"Home\"}},{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/devdojo.co.in\\\/index.php\\\/category\\\/backend\\\/python\\\/#listItem\",\"position\":3,\"name\":\"Python\",\"item\":\"https:\\\/\\\/devdojo.co.in\\\/index.php\\\/category\\\/backend\\\/python\\\/\",\"nextItem\":{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/devdojo.co.in\\\/index.php\\\/2026\\\/10\\\/15\\\/fastapi-tutorial\\\/#listItem\",\"name\":\"FastAPI Tutorial: Build a REST API in Python Step by Step\"},\"previousItem\":{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/devdojo.co.in\\\/index.php\\\/category\\\/backend\\\/#listItem\",\"name\":\"Backend\"}},{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/devdojo.co.in\\\/index.php\\\/2026\\\/10\\\/15\\\/fastapi-tutorial\\\/#listItem\",\"position\":4,\"name\":\"FastAPI Tutorial: Build a REST API in Python Step by Step\",\"previousItem\":{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/devdojo.co.in\\\/index.php\\\/category\\\/backend\\\/python\\\/#listItem\",\"name\":\"Python\"}}]},{\"@type\":\"Organization\",\"@id\":\"https:\\\/\\\/devdojo.co.in\\\/#organization\",\"name\":\"DevDojo\",\"description\":\"Empowering Developers\",\"url\":\"https:\\\/\\\/devdojo.co.in\\\/\"},{\"@type\":\"Person\",\"@id\":\"https:\\\/\\\/devdojo.co.in\\\/index.php\\\/author\\\/lokendra\\\/#author\",\"url\":\"https:\\\/\\\/devdojo.co.in\\\/index.php\\\/author\\\/lokendra\\\/\",\"name\":\"Lokendra\",\"image\":{\"@type\":\"ImageObject\",\"@id\":\"https:\\\/\\\/devdojo.co.in\\\/index.php\\\/2026\\\/10\\\/15\\\/fastapi-tutorial\\\/#authorImage\",\"url\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/99f102f0cc9080237ce54b91304fd1b3b6fa29925cbaeba93cd796872e58b45c?s=96&d=mm&r=g\",\"width\":96,\"height\":96,\"caption\":\"Lokendra\"}},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/devdojo.co.in\\\/index.php\\\/2026\\\/10\\\/15\\\/fastapi-tutorial\\\/#webpage\",\"url\":\"https:\\\/\\\/devdojo.co.in\\\/index.php\\\/2026\\\/10\\\/15\\\/fastapi-tutorial\\\/\",\"name\":\"FastAPI Tutorial: Build a REST API in Python Step by Step\",\"description\":\"Learn FastAPI step by step: build a CRUD REST API in Python with Pydantic validation, SQLModel and SQLite, automatic docs, pytest tests and deployment tips.\",\"inLanguage\":\"en-US\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/devdojo.co.in\\\/#website\"},\"breadcrumb\":{\"@id\":\"https:\\\/\\\/devdojo.co.in\\\/index.php\\\/2026\\\/10\\\/15\\\/fastapi-tutorial\\\/#breadcrumblist\"},\"author\":{\"@id\":\"https:\\\/\\\/devdojo.co.in\\\/index.php\\\/author\\\/lokendra\\\/#author\"},\"creator\":{\"@id\":\"https:\\\/\\\/devdojo.co.in\\\/index.php\\\/author\\\/lokendra\\\/#author\"},\"image\":{\"@type\":\"ImageObject\",\"url\":\"https:\\\/\\\/devdojo.co.in\\\/wp-content\\\/uploads\\\/2026\\\/10\\\/fastapi-tutorial.jpg\",\"@id\":\"https:\\\/\\\/devdojo.co.in\\\/index.php\\\/2026\\\/10\\\/15\\\/fastapi-tutorial\\\/#mainImage\",\"width\":1200,\"height\":630,\"caption\":\"FastAPI tutorial banner: a request flows from a client through FastAPI validation to a SQLModel database and returns JSON\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/devdojo.co.in\\\/index.php\\\/2026\\\/10\\\/15\\\/fastapi-tutorial\\\/#mainImage\"},\"datePublished\":\"2026-10-15T18:55:30+00:00\",\"dateModified\":\"2026-10-15T18:55:30+00:00\"},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/devdojo.co.in\\\/#website\",\"url\":\"https:\\\/\\\/devdojo.co.in\\\/\",\"name\":\"DevDojo\",\"description\":\"Empowering Developers\",\"inLanguage\":\"en-US\",\"publisher\":{\"@id\":\"https:\\\/\\\/devdojo.co.in\\\/#organization\"}}]}\n\t\t<\/script>\n\t\t<!-- All in One SEO -->\n\n","aioseo_head_json":{"title":"FastAPI Tutorial: Build a REST API in Python Step by Step","description":"Learn FastAPI step by step: build a CRUD REST API in Python with Pydantic validation, SQLModel and SQLite, automatic docs, pytest tests and deployment tips.","canonical_url":"https:\/\/devdojo.co.in\/index.php\/2026\/10\/15\/fastapi-tutorial\/","robots":"max-image-preview:large","keywords":"","webmasterTools":{"miscellaneous":""},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"BlogPosting","@id":"https:\/\/devdojo.co.in\/index.php\/2026\/10\/15\/fastapi-tutorial\/#blogposting","name":"FastAPI Tutorial: Build a REST API in Python Step by Step","headline":"FastAPI Tutorial: Build a REST API in Python Step by Step","author":{"@id":"https:\/\/devdojo.co.in\/index.php\/author\/lokendra\/#author"},"publisher":{"@id":"https:\/\/devdojo.co.in\/#organization"},"image":{"@type":"ImageObject","url":"https:\/\/devdojo.co.in\/wp-content\/uploads\/2026\/10\/fastapi-tutorial.jpg","width":1200,"height":630,"caption":"FastAPI tutorial banner: a request flows from a client through FastAPI validation to a SQLModel database and returns JSON"},"datePublished":"2026-10-15T18:55:30+00:00","dateModified":"2026-10-15T18:55:30+00:00","inLanguage":"en-US","mainEntityOfPage":{"@id":"https:\/\/devdojo.co.in\/index.php\/2026\/10\/15\/fastapi-tutorial\/#webpage"},"isPartOf":{"@id":"https:\/\/devdojo.co.in\/index.php\/2026\/10\/15\/fastapi-tutorial\/#webpage"},"articleSection":"Backend, FastAPI, Python, Backend Tutorial, FastAPI, Pydantic, Python, REST API, SQLModel"},{"@type":"BreadcrumbList","@id":"https:\/\/devdojo.co.in\/index.php\/2026\/10\/15\/fastapi-tutorial\/#breadcrumblist","itemListElement":[{"@type":"ListItem","@id":"https:\/\/devdojo.co.in\/#listItem","position":1,"name":"Home","item":"https:\/\/devdojo.co.in\/","nextItem":{"@type":"ListItem","@id":"https:\/\/devdojo.co.in\/index.php\/category\/backend\/#listItem","name":"Backend"}},{"@type":"ListItem","@id":"https:\/\/devdojo.co.in\/index.php\/category\/backend\/#listItem","position":2,"name":"Backend","item":"https:\/\/devdojo.co.in\/index.php\/category\/backend\/","nextItem":{"@type":"ListItem","@id":"https:\/\/devdojo.co.in\/index.php\/category\/backend\/python\/#listItem","name":"Python"},"previousItem":{"@type":"ListItem","@id":"https:\/\/devdojo.co.in\/#listItem","name":"Home"}},{"@type":"ListItem","@id":"https:\/\/devdojo.co.in\/index.php\/category\/backend\/python\/#listItem","position":3,"name":"Python","item":"https:\/\/devdojo.co.in\/index.php\/category\/backend\/python\/","nextItem":{"@type":"ListItem","@id":"https:\/\/devdojo.co.in\/index.php\/2026\/10\/15\/fastapi-tutorial\/#listItem","name":"FastAPI Tutorial: Build a REST API in Python Step by Step"},"previousItem":{"@type":"ListItem","@id":"https:\/\/devdojo.co.in\/index.php\/category\/backend\/#listItem","name":"Backend"}},{"@type":"ListItem","@id":"https:\/\/devdojo.co.in\/index.php\/2026\/10\/15\/fastapi-tutorial\/#listItem","position":4,"name":"FastAPI Tutorial: Build a REST API in Python Step by Step","previousItem":{"@type":"ListItem","@id":"https:\/\/devdojo.co.in\/index.php\/category\/backend\/python\/#listItem","name":"Python"}}]},{"@type":"Organization","@id":"https:\/\/devdojo.co.in\/#organization","name":"DevDojo","description":"Empowering Developers","url":"https:\/\/devdojo.co.in\/"},{"@type":"Person","@id":"https:\/\/devdojo.co.in\/index.php\/author\/lokendra\/#author","url":"https:\/\/devdojo.co.in\/index.php\/author\/lokendra\/","name":"Lokendra","image":{"@type":"ImageObject","@id":"https:\/\/devdojo.co.in\/index.php\/2026\/10\/15\/fastapi-tutorial\/#authorImage","url":"https:\/\/secure.gravatar.com\/avatar\/99f102f0cc9080237ce54b91304fd1b3b6fa29925cbaeba93cd796872e58b45c?s=96&d=mm&r=g","width":96,"height":96,"caption":"Lokendra"}},{"@type":"WebPage","@id":"https:\/\/devdojo.co.in\/index.php\/2026\/10\/15\/fastapi-tutorial\/#webpage","url":"https:\/\/devdojo.co.in\/index.php\/2026\/10\/15\/fastapi-tutorial\/","name":"FastAPI Tutorial: Build a REST API in Python Step by Step","description":"Learn FastAPI step by step: build a CRUD REST API in Python with Pydantic validation, SQLModel and SQLite, automatic docs, pytest tests and deployment tips.","inLanguage":"en-US","isPartOf":{"@id":"https:\/\/devdojo.co.in\/#website"},"breadcrumb":{"@id":"https:\/\/devdojo.co.in\/index.php\/2026\/10\/15\/fastapi-tutorial\/#breadcrumblist"},"author":{"@id":"https:\/\/devdojo.co.in\/index.php\/author\/lokendra\/#author"},"creator":{"@id":"https:\/\/devdojo.co.in\/index.php\/author\/lokendra\/#author"},"image":{"@type":"ImageObject","url":"https:\/\/devdojo.co.in\/wp-content\/uploads\/2026\/10\/fastapi-tutorial.jpg","@id":"https:\/\/devdojo.co.in\/index.php\/2026\/10\/15\/fastapi-tutorial\/#mainImage","width":1200,"height":630,"caption":"FastAPI tutorial banner: a request flows from a client through FastAPI validation to a SQLModel database and returns JSON"},"primaryImageOfPage":{"@id":"https:\/\/devdojo.co.in\/index.php\/2026\/10\/15\/fastapi-tutorial\/#mainImage"},"datePublished":"2026-10-15T18:55:30+00:00","dateModified":"2026-10-15T18:55:30+00:00"},{"@type":"WebSite","@id":"https:\/\/devdojo.co.in\/#website","url":"https:\/\/devdojo.co.in\/","name":"DevDojo","description":"Empowering Developers","inLanguage":"en-US","publisher":{"@id":"https:\/\/devdojo.co.in\/#organization"}}]},"og:locale":"en_US","og:site_name":"DevDojo - Empowering Developers","og:type":"article","og:title":"FastAPI Tutorial: Build a REST API in Python Step by Step","og:description":"Learn FastAPI step by step: build a CRUD REST API in Python with Pydantic validation, SQLModel and SQLite, automatic docs, pytest tests and deployment tips.","og:url":"https:\/\/devdojo.co.in\/index.php\/2026\/10\/15\/fastapi-tutorial\/","article:published_time":"2026-10-15T18:55:30+00:00","article:modified_time":"2026-10-15T18:55:30+00:00","twitter:card":"summary_large_image","twitter:title":"FastAPI Tutorial: Build a REST API in Python Step by Step","twitter:description":"Learn FastAPI step by step: build a CRUD REST API in Python with Pydantic validation, SQLModel and SQLite, automatic docs, pytest tests and deployment tips."},"aioseo_meta_data":{"post_id":"203","title":"FastAPI Tutorial: Build a REST API in Python Step by Step","description":"Learn FastAPI step by step: build a CRUD REST API in Python with Pydantic validation, SQLModel and SQLite, automatic docs, pytest tests and deployment tips.","keywords":null,"keyphrases":{"focus":{"keyphrase":"FastAPI tutorial","score":69},"additional":[]},"primary_term":null,"canonical_url":null,"og_title":"FastAPI Tutorial: Build a REST API in Python Step by Step","og_description":"Learn FastAPI step by step: build a CRUD REST API in Python with Pydantic validation, SQLModel and SQLite, automatic docs, pytest tests and deployment tips.","og_object_type":"default","og_image_type":"default","og_image_url":null,"og_image_width":null,"og_image_height":null,"og_image_custom_url":null,"og_image_custom_fields":null,"og_video":"","og_custom_url":null,"og_article_section":null,"og_article_tags":null,"twitter_use_og":false,"twitter_card":"default","twitter_image_type":"default","twitter_image_url":null,"twitter_image_custom_url":null,"twitter_image_custom_fields":null,"twitter_title":null,"twitter_description":null,"schema":{"blockGraphs":[],"customGraphs":[],"default":{"data":{"Article":[],"Course":[],"Dataset":[],"FAQPage":[],"Movie":[],"Person":[],"Product":[],"ProductReview":[],"Car":[],"Recipe":[],"Service":[],"SoftwareApplication":[],"WebPage":[]},"graphName":"BlogPosting","isEnabled":true},"graphs":[]},"schema_type":"default","schema_type_options":null,"pillar_content":false,"robots_default":true,"robots_noindex":false,"robots_noarchive":false,"robots_nosnippet":false,"robots_nofollow":false,"robots_noimageindex":false,"robots_noodp":false,"robots_notranslate":false,"robots_max_snippet":"-1","robots_max_videopreview":"-1","robots_max_imagepreview":"large","priority":0,"frequency":"default","local_seo":null,"breadcrumb_settings":null,"limit_modified_date":false,"ai":{"faqs":[],"keyPoints":[],"schemas":[],"titles":[],"descriptions":[],"socialPosts":{"email":{"subject":"","preview":"","content":""},"linkedin":[],"twitter":[],"facebook":[],"instagram":[]}},"created":"2026-10-15 18:26:48","updated":"2026-10-15 18:55:30","focus_keyword":"FastAPI tutorial","additional_keywords":null,"truseo_locale":null,"seo_analyzer_scan_date":null},"aioseo_breadcrumb":"<div class=\"aioseo-breadcrumbs\"><span class=\"aioseo-breadcrumb\">\n\t\t\t<a href=\"https:\/\/devdojo.co.in\/\" title=\"Home\">Home<\/a>\n\t\t<\/span><span class=\"aioseo-breadcrumb-separator\">\u00bb<\/span><span class=\"aioseo-breadcrumb\">\n\t\t\t<a href=\"https:\/\/devdojo.co.in\/index.php\/category\/backend\/\" title=\"Backend\">Backend<\/a>\n\t\t<\/span><span class=\"aioseo-breadcrumb-separator\">\u00bb<\/span><span class=\"aioseo-breadcrumb\">\n\t\t\t<a href=\"https:\/\/devdojo.co.in\/index.php\/category\/backend\/python\/\" title=\"Python\">Python<\/a>\n\t\t<\/span><span class=\"aioseo-breadcrumb-separator\">\u00bb<\/span><span class=\"aioseo-breadcrumb\">\n\t\t\tFastAPI Tutorial: Build a REST API in Python Step by Step\n\t\t<\/span><\/div>","aioseo_breadcrumb_json":[{"label":"Home","link":"https:\/\/devdojo.co.in\/"},{"label":"Backend","link":"https:\/\/devdojo.co.in\/index.php\/category\/backend\/"},{"label":"Python","link":"https:\/\/devdojo.co.in\/index.php\/category\/backend\/python\/"},{"label":"FastAPI Tutorial: Build a REST API in Python Step by Step","link":"https:\/\/devdojo.co.in\/index.php\/2026\/10\/15\/fastapi-tutorial\/"}],"_links":{"self":[{"href":"https:\/\/devdojo.co.in\/index.php\/wp-json\/wp\/v2\/posts\/203","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devdojo.co.in\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devdojo.co.in\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devdojo.co.in\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/devdojo.co.in\/index.php\/wp-json\/wp\/v2\/comments?post=203"}],"version-history":[{"count":1,"href":"https:\/\/devdojo.co.in\/index.php\/wp-json\/wp\/v2\/posts\/203\/revisions"}],"predecessor-version":[{"id":204,"href":"https:\/\/devdojo.co.in\/index.php\/wp-json\/wp\/v2\/posts\/203\/revisions\/204"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devdojo.co.in\/index.php\/wp-json\/wp\/v2\/media\/202"}],"wp:attachment":[{"href":"https:\/\/devdojo.co.in\/index.php\/wp-json\/wp\/v2\/media?parent=203"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devdojo.co.in\/index.php\/wp-json\/wp\/v2\/categories?post=203"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devdojo.co.in\/index.php\/wp-json\/wp\/v2\/tags?post=203"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}