Build a Task Triage API with FastAPI
Build a validated FastAPI task API with interactive Swagger documentation.
Introduction
30 Second Summary
A growing task list becomes harder to manage when urgent work looks the same as everything else. Clear priorities help the right task rise to the top.
In this project, you will build a task triage REST API with Python and FastAPI. You will manage a queue of work through validated browser requests.
What You'll Build
You’ll use Swagger UI to create tasks in your browser. You’ll watch the API return task data or reject invalid requests with clear errors.
By the end of this project, you'll have:
- A task creation flow that accepts a title plus a priority. Each new task comes back with its own generated ID.
- A complete task workflow that lets you list tasks. You can retrieve individual tasks. You can also replace or delete them.
- Clear validation and error responses that reject unsupported priorities. They also reject text task IDs and report missing tasks.
- A Secret Mission: Add completion and priority filters that narrow the task queue through optional query parameters.
Are there any prerequisites?
You should already feel comfortable with Python functions, classes, dictionaries, and type hints. You’ll need Python 3.10 or later on Windows with Visual Studio Code. Step 1 checks Microsoft Edge and sets up the remaining project packages.
Before We Start
A clear audience gives your Task Triage API a purpose before any hands-on work begins. This decision keeps every later operation focused on a real need.
Set Up the FastAPI Project
A project-specific environment keeps your Task Triage API from changing the packages used by other Python projects.
A virtual environment isolates the interpreter inside fastapi-task-api. Exact pins give FastAPI 0.156.1 plus Pydantic 2.14.1 a reproducible home.
In this step, get ready to:
- Confirm Microsoft Edge is available for browser testing.
- Create the fastapi-task-api folder with its pinned dependency file.
- Install the pinned dependencies inside an activated .venv environment.
Prepare the project files
Microsoft Edge provides the browser for your API checks. Confirming it now keeps the later Swagger UI demo within reach.
- Press the Windows key to open Windows search.
- Type Microsoft Edge into the search field.
- Press Enter to launch the browser.
✔️ Microsoft Edge opens
Microsoft Edge is ready for the browser-based API checks later in the project.
ⓧ Microsoft Edge is missing
The official download page provides the Windows installer. The installer may request permission to make changes to your device.
- Open the official Microsoft Edge download page.
- Select the Windows download option on the page.
- Run the downloaded installer.
- Approve the permission request if Windows displays one.
- Launch Microsoft Edge from Windows search after the installation finishes.
Microsoft Edge opens when the installation is complete.
The project folder gives every file a predictable location. You will place it on your Desktop so it is easy to find again.
- Press Windows+E to open File Explorer.
- Select Desktop in the left navigation pane.
- Select New in the File Explorer toolbar.
- Select Folder.
- Enter fastapi-task-api as the folder name.
- Press Enter to create the folder.
You now have an empty fastapi-task-api folder on your Desktop.
Visual Studio Code keeps the dependency file beside the environment that uses it. Opening the folder also gives the integrated terminal the correct starting location.
- Press the Windows key to open Windows search.
- Type Visual Studio Code into the search field.
- Press Enter to launch Visual Studio Code.
- Select File in the top menu.
- Select Open Folder.
- Select Desktop in the folder picker.
- Select the fastapi-task-api folder.
- Confirm the folder selection.
Visual Studio Code may display a Workspace Trust prompt. You created this folder yourself.
- Confirm that you trust the folder if the Workspace Trust prompt appears.
The Explorer sidebar now shows fastapi-task-api as the open folder.
A requirements file records the exact dependency versions for this project. Installing from this file keeps every fresh environment aligned.
- Right-click the fastapi-task-api folder in the Explorer sidebar.
- Select New File.
- Enter requirements.txt as the file name.
- Add the pinned dependencies to requirements.txt by pasting this content:
fastapi[standard]==0.156.1
pydantic==2.14.1
What does this file do?
- The first line installs the FastAPI standard package at exactly 0.156.1.
- The second line installs Pydantic at exactly 2.14.1.
- The == operator keeps future installations on the tested versions.
- Press Ctrl+S to save requirements.txt.
- Confirm requirements.txt appears directly beneath fastapi-task-api in the Explorer sidebar.
Can't find requirements.txt?
Check that the file name is exactly requirements.txt. Confirm that it sits directly inside fastapi-task-api.
Make sure the file contains exactly two dependency lines. Help me check my requirements file.
✔️ Awesome, I've got everything!
Great. Double-check that you saved requirements.txt.
ⓧ I'd like to double check the full code
fastapi[standard]==0.156.1
pydantic==2.14.1
Isolate the project environment
The integrated terminal starts inside the folder open in Visual Studio Code. The .venv directory stores a separate Python interpreter with this project's packages.
- Select Terminal in the Visual Studio Code top menu.
- Select New Terminal.
- Open the terminal profile menu in the terminal panel.
- Select Command Prompt as the terminal profile.
- Start a new Command Prompt terminal from the terminal panel.
The new terminal prompt ends inside the open fastapi-task-api folder.
- Create the project-local virtual environment by running this command:
py -m venv .venv
What does this command do?
- The py launcher starts the installed Python interpreter.
- The venv module creates an isolated Python environment.
- The final .venv value names the environment folder inside your project.
- Wait for the terminal prompt to return.
- Confirm the .venv folder appears in the Explorer sidebar.
The new .venv folder proves that the isolated environment exists.
Virtual environment missing?
Confirm that the terminal prompt ends inside fastapi-task-api. A different location places .venv outside this project.
Wait for the command to finish before checking the Explorer sidebar. Help me troubleshoot the missing environment.
Activation places the environment's Python executable first on the terminal path. The terminal prompt also gains a visible environment marker.
- Activate the project environment by running this command:
.venv\Scripts\activate
What does activation change?
Activation makes python resolve to the interpreter inside .venv. Package installations now stay inside this project.
The terminal prompt begins with (.venv) after activation succeeds.
Activation marker missing?
Confirm that the terminal profile is Command Prompt. Check that the .venv folder exists in the Explorer sidebar.
Run the activation command from the terminal inside fastapi-task-api. Help me activate this environment.
The requirements file now acts as the installation plan for the active environment. The terminal prints progress while it downloads the packages.
- Install the pinned dependencies by running this command:
python -m pip install -r requirements.txt
What does this command do?
- The active Python interpreter runs pip inside .venv.
- The -r option reads the package pins from requirements.txt.
- The installation output records the packages added to this environment.
The terminal shows package download progress. It finishes by reporting a successful installation.
Dependency installation failing?
Confirm that the terminal prompt starts with (.venv). Check that requirements.txt is saved inside fastapi-task-api.
A network interruption can stop a package download. Help me diagnose this installation.
Verify the isolated installation
The interpreter path proves which Python installation receives your commands. The package list proves which dependency versions landed there.
Before you run the checks, which folder do you expect the first Python path to include?
- Verify the interpreter path plus the installed package versions by running these commands:
where python
python -m pip list
What do these checks prove?
- The first command lists the Python executables available to the active terminal.
- The second command lists the packages installed for the active interpreter.
- Together the results connect the pinned packages to .venv.
The first path includes .venv\Scripts\python. This confirms that the terminal is using the project environment.
The package list includes FastAPI 0.156.1 plus Pydantic 2.14.1.
Seeing the wrong path or versions?
Activate .venv again if its Python path is missing. Reinstall from requirements.txt if either package version differs.
Repeat both verification checks in the same activated terminal. Help me correct this environment.
Your isolated FastAPI environment now contains the exact packages this project needs. Next, you will create the first routes and watch the backend respond in Microsoft Edge.
Launch an Under-Validated Endpoint
Your isolated project is ready. The next goal is a FastAPI server you can reach from the browser.
An HTTP path parameter carries part of a URL into a route function. This first version leaves that value untyped so Swagger UI can expose the validation gap.
In this step, get ready to:
- Create a FastAPI app with a visible root response.
- Launch the local development server.
- Test an untyped task ID through Swagger UI.
Create the first live route
A FastAPI instance registers the functions that handle incoming requests. The root route gives you a small response that proves the server can run your code.
- In the Visual Studio Code Explorer toolbar, click the New File icon.
- Enter main.py as the file name.
- Press Enter.
- Add the FastAPI instance plus the root route to main.py by pasting this code:
from fastapi import FastAPI
app = FastAPI(title="Task Triage API")
@app.get("/")
def read_root():
return {"message": "Task Triage API"}
What does this code do?
- The FastAPI import provides the class that creates your API application.
- The app variable holds the application instance with the title Task Triage API.
- The read_root() function returns the response for requests to /.
- Save main.py.
- Confirm main.py appears beside requirements.txt in the Explorer sidebar.
Can't see main.py?
- Check that the Explorer sidebar still shows the fastapi-task-api folder.
- Confirm main.py sits beside requirements.txt.
- Use this file-location check if the file appears in a different folder.
Development mode watches main.py for saved changes. It keeps the integrated terminal occupied while your API remains available.
- Return to the integrated terminal with the activated .venv environment.
- Start the development server by running this command:
fastapi dev
What does this command do?
- Development mode discovers the app instance in main.py.
- The server reloads when you save code changes.
- The running process keeps this terminal occupied while the API handles requests.
- Switch back to Microsoft Edge from earlier.
- Enter http://127.0.0.1:8000 in the address bar.
- Press Enter.
You'll see a JSON response with message set to Task Triage API.
That is your first backend win. The browser is now receiving data from your own route.
Server not starting?
- Confirm the integrated terminal still uses the activated virtual environment from the previous step.
- Check that main.py is saved inside fastapi-task-api.
- Use this server-startup check if the command does not start the local API.
Add the untyped task route
A route can capture a value from its URL through a path parameter. This first task lookup receives task_id without declaring its expected type.
- Switch back to main.py in Visual Studio Code.
- Place your cursor below the read_root() function.
- Add the untyped task lookup route by pasting this code:
@app.get("/tasks/{task_id}")
def get_task(task_id):
return {"task_id": task_id}
What does this route do?
- The route decorator exposes a URL with a task_id slot.
- The get_task() function receives the captured value through task_id.
- The response places that captured value in a JSON object.
- Save main.py.
- Switch back to Microsoft Edge.
- Enter http://127.0.0.1:8000/docs in the address bar.
- Press Enter.
- Select GET /tasks/{task_id} to expand the route.
You'll see the root route plus the new task lookup route in Swagger UI. The task lookup includes an input field for task_id.
Route missing from Swagger UI?
- Confirm main.py was saved after you added the route.
- Check that the decorator reads @app.get("/tasks/{task_id}").
- Use this route-registration check if the route still does not appear.
✔️ Awesome, I've got everything!
Great. Your saved main.py now contains both routes.
ⓧ I'd like to double check the full code
from fastapi import FastAPI
app = FastAPI(title="Task Triage API")
@app.get("/")
def read_root():
return {"message": "Task Triage API"}
@app.get("/tasks/{task_id}")
def get_task(task_id):
return {"task_id": task_id}
Test the validation gap
The browser test now checks whether the task path has an enforceable data contract. The result reveals what reaches your route function.
Before you execute the request, do you think the text value will be rejected or echoed back?
- Click Try it out.
- Enter not-a-number in the task_id field.
- Click Execute.
You'll see the request succeed. The response body echoes not-a-number as the value of task_id.
Why did the text get through?
The route gives task_id no type annotation. FastAPI therefore has no integer contract to enforce for this value.
That successful response is the intended shortfall. The next step adds the typed contract that closes this gap.
You have proved that a running endpoint can still accept the wrong input. Next, you'll add typed models that turn Python annotations into an enforceable API contract.
Add Typed Models and Validation
Your first FastAPI route exposed the weakness in an untyped path parameter. Swagger UI proved that text could reach backend logic where an integer belongs.
Python type hints will protect task IDs. Pydantic models will protect task bodies from unsupported priority values.
In this step, get ready to:
- Define the task data contract.
- Require integer task IDs.
- Add typed task collection routes.
Define the task contract
The Literal type limits priority to three supported values. TaskCreate turns that rule into a request body contract.
- In main.py, replace the opening section through the existing app line with this code:
from typing import Literal
from fastapi import FastAPI
from pydantic import BaseModel
Priority = Literal["low", "medium", "high"]
app = FastAPI(title="Task Triage API")
class TaskCreate(BaseModel):
title: str
priority: Priority = "medium"
What does this code do?
- The Priority alias accepts low, medium, or high.
- The TaskCreate model requires a text title.
- The default priority is medium when a request omits that field.
- The existing app instance keeps the same API title.
- Save main.py.
- Return to the integrated terminal from earlier.
- Confirm the development server completes its reload cycle.
You will see the server finish reloading without a Python traceback. The new request model is valid Python code.
Server not reloading cleanly?
Check that Literal comes from typing. Check that BaseModel comes from pydantic.
Confirm every priority value uses matching quotation marks. Help me fix the model setup in main.py.
A stored task needs fields that clients cannot choose. Task adds the generated ID plus the completion state.
- Add the response model plus the in-memory state below the TaskCreate class by pasting this code:
class Task(TaskCreate):
id: int
completed: bool = False
tasks: dict[int, Task] = {}
next_id = 1
How is task state represented?
- The Task model inherits the request fields from TaskCreate.
- The id field holds the generated integer identifier.
- The completed field starts as False.
- The tasks dictionary stores each task under its ID.
- The next_id counter starts the ID sequence at 1.
- Save main.py.
- Return to the integrated terminal from earlier.
- Confirm the development server completes another reload cycle.
You will see the server return to its running state. Your API now has a typed task shape plus an in-memory store.
Seeing a model or dictionary error?
Make sure Task appears below TaskCreate. The child model needs its parent to exist first.
Check that the dictionary annotation uses Task as its value type. Help me debug the Task model and in-memory store.
Protect task IDs
FastAPI uses a route function's type annotations as a validation boundary. Changing task_id to an integer makes malformed IDs fail before get_task runs.
- In main.py, replace the existing get_task route with this code:
@app.get("/tasks/{task_id}")
def get_task(task_id: int):
return {"task_id": task_id}
Why does one annotation matter?
- The int annotation tells FastAPI to parse the path value as an integer.
- A valid integer reaches get_task as an integer.
- Text fails validation before the route body runs.
- Save main.py.
- Return to Microsoft Edge.
- Refresh the Swagger UI page from earlier.
Before you repeat the earlier request, do you expect not-a-number to reach the route this time?
- Expand GET /tasks/{task_id}.
- Click Try it out.
- Enter not-a-number in the task_id field.
- Click Execute.
You will see a structured validation response that identifies the path field. The route no longer echoes the text value.
You have closed the validation gap that the previous endpoint exposed.
Still seeing the text echoed?
Confirm the route function says task_id: int. Refresh Swagger UI after the development server reloads.
Make sure you are testing GET /tasks/{task_id}. Help me find why the text task ID is still accepted.
Add typed task collection routes
A response model defines the shape clients receive from a route. FastAPI also adds that shape to the generated OpenAPI schema.
- Add the task list route above the existing get_task route by pasting this code:
@app.get("/tasks", response_model=list[Task])
def list_tasks():
return list(tasks.values())
What does the list route return?
- The response_model declaration describes a list of Task objects.
- The list_tasks function reads every value from the in-memory dictionary.
- The returned dictionary view becomes a regular list.
- Save main.py.
- Refresh Swagger UI in Microsoft Edge.
- Expand GET /tasks.
- Click Try it out.
- Click Execute.
You will see an empty JSON array. That result confirms the route can read the new task store.
Task list route missing?
Confirm the list_tasks route sits above GET /tasks/{task_id} in main.py. Save the file before refreshing Swagger UI.
Check that the decorator path is exactly /tasks. Help me make the list route appear in Swagger UI.
Creating a task requires a generated ID that remains outside the request body. The route builds a full Task from the validated input.
- Add create_task directly below the existing get_task route by pasting this code:
@app.post("/tasks", response_model=Task)
def create_task(task: TaskCreate):
global next_id
new_task = Task(
id=next_id,
title=task.title,
priority=task.priority,
)
tasks[next_id] = new_task
next_id += 1
return new_task
How does task creation work?
- The TaskCreate annotation validates the incoming request body.
- The new_task object receives the current integer ID.
- The task enters the tasks dictionary under that ID.
- The next_id counter advances before the route returns.
- The Task response model exposes the generated fields in the response.
- Save main.py.
- Return to the integrated terminal from earlier.
- Confirm the development server completes its reload cycle.
You will see the server return to its running state. The typed create route is ready for a browser request.
Create route causing a reload error?
Check that global next_id appears before the counter changes. Confirm new_task is created with Task.
Make sure the route sits outside the get_task function. Help me debug the create_task route.
✔️ Awesome, I've got everything!
Great. Double-check that you saved main.py.
ⓧ I'd like to double check the full code
from typing import Literal
from fastapi import FastAPI
from pydantic import BaseModel
Priority = Literal["low", "medium", "high"]
app = FastAPI(title="Task Triage API")
class TaskCreate(BaseModel):
title: str
priority: Priority = "medium"
class Task(TaskCreate):
id: int
completed: bool = False
tasks: dict[int, Task] = {}
next_id = 1
@app.get("/")
def read_root():
return {"message": "Task Triage API"}
@app.get("/tasks", response_model=list[Task])
def list_tasks():
return list(tasks.values())
@app.get("/tasks/{task_id}")
def get_task(task_id: int):
return {"task_id": task_id}
@app.post("/tasks", response_model=Task)
def create_task(task: TaskCreate):
global next_id
new_task = Task(
id=next_id,
title=task.title,
priority=task.priority,
)
tasks[next_id] = new_task
next_id += 1
return new_task
A successful request proves the model can accept supported data. The validation checks will show how the same contract rejects malformed input.
- Refresh Swagger UI in Microsoft Edge.
- Expand POST /tasks.
- Click Try it out.
- Enter Write API docs for title in the JSON request body.
- Enter high for priority.
- Click Execute.
Great work. The response shows the generated integer ID.
You will also see high as the priority. The completion state is false.
- Expand GET /tasks.
- Click Try it out.
- Click Execute.
You will see the task you created in the list response. The in-memory collection now holds a typed task.
Before you test the invalid inputs, which field do you expect each response to identify?
- Return to POST /tasks.
- Click Try it out.
- Enter Write API docs for title.
- Enter urgent for priority.
- Click Execute.
You will see a structured validation response that identifies priority as the rejected field. The route logic does not create the invalid task.
- Return to GET /tasks/{task_id}.
- Click Try it out.
- Enter not-a-number in the task_id field.
- Click Execute.
You will see another structured validation response. This one identifies task_id as the rejected path field.
Validation test behaving differently?
Refresh Swagger UI after saving main.py. Confirm the unsupported priority is outside the three values in Priority.
Check that you entered the text ID in the path parameter field. Help me troubleshoot the validation responses.
Your API now creates typed tasks while rejecting malformed IDs and priorities. Next, you will complete the replace and delete workflow with explicit missing-task errors.
Finish CRUD and Error Handling
Your typed routes now create valid tasks. They also list them while rejecting malformed IDs or priorities.
This step completes the task lifecycle with replacement plus deletion. FastAPI route logic will also return a clear HTTP response when an ID has no matching task.
In this step, get ready to:
- Define the contract for replacing a task.
- Return an explicit error for missing task IDs.
- Verify the full task lifecycle.
Build task replacement
A PUT request represents full replacement in this API. TaskUpdate describes the new title plus priority plus completion state.
- Switch back to main.py from earlier.
- Locate the Task model.
- Add the replacement model immediately below Task by pasting this code:
class TaskUpdate(TaskCreate):
completed: bool = False
What does this model do?
- TaskUpdate inherits the title plus priority fields from TaskCreate.
- The completed field lets the replacement request set the task state.
- The default value keeps a replacement incomplete when the request omits that field.
- Save main.py.
You'll see the development server reload main.py. The saved model is now part of the generated API schema.
Server stopped after the model change?
Check that TaskUpdate is aligned with the other class declarations. Confirm that completed is indented inside the class.
A missing colon can also stop the reload. Help me check my TaskUpdate model.
The replacement route uses the ID from the URL. Its request body supplies the new task state.
- Scroll below the create_task route in main.py.
- Add the replacement route by pasting this code:
@app.put("/tasks/{task_id}", response_model=Task)
def replace_task(task_id: int, update: TaskUpdate):
get_task(task_id)
updated_task = Task(
id=task_id,
title=update.title,
priority=update.priority,
completed=update.completed,
)
tasks[task_id] = updated_task
return updated_task
What does this route do?
- The route receives a typed path ID plus a validated TaskUpdate body.
- The call to get_task() gives resource checks one shared lookup point.
- Task keeps the path ID while taking the replacement fields from update.
- The assignment overwrites the stored task under the same dictionary key.
- Save main.py.
- Return to Swagger UI in Microsoft Edge from earlier.
- Refresh the page.
The API documentation now includes PUT /tasks/{task_id}.
- Expand POST /tasks.
- Click Try it out.
- Set the title in the JSON editor to Prepare release notes.
- Set the priority to high.
- Click Execute.
The response shows id as 1. It shows completed as false.
- Expand PUT /tasks/{task_id}.
- Click Try it out.
- Enter 1 for the task ID.
- Set the title in the JSON editor to Publish release notes.
- Set the priority to high.
- Set completed to true.
- Click Execute.
The response keeps id as 1. It shows Publish release notes with completed set to true.
Replacement route missing or failing?
Refresh Swagger UI after the development server finishes reloading. Confirm that replace_task sits below create_task in main.py.
Create a task again if the server reloaded after your last request. Help me troubleshoot the replacement route.
Protect task deletion
A typed ID proves that the path value is an integer. The shared lookup also needs to confirm that the dictionary contains the requested task.
HTTPException turns a missing task into a structured 404 response. The same lookup can protect retrieval plus replacement plus deletion.
- Locate the existing FastAPI import near the top of main.py.
- Replace that import with this line:
from fastapi import FastAPI, HTTPException
What does this import change?
HTTPException lets route logic stop a request with a specific status code. FastAPI converts the exception into a JSON error response.
- Save main.py.
The terminal shows the server reload after the saved import.
- Locate the current get_task route in main.py.
- Replace the entire route with this code:
@app.get("/tasks/{task_id}", response_model=Task)
def get_task(task_id: int):
task = tasks.get(task_id)
if task is None:
raise HTTPException(status_code=404, detail="Task not found")
return task
How does the lookup protect the API?
- The response_model declaration documents a successful response as a Task.
- tasks.get(task_id) returns the stored task when the key exists.
- A missing key raises HTTPException with status_code=404.
- A successful lookup returns the typed task.
- Save main.py.
- Return to Swagger UI.
- Refresh the page.
Before you test the lookup, do you think an integer ID with no matching dictionary key will succeed?
- Expand GET /tasks/{task_id}.
- Click Try it out.
- Enter 999 for the task ID.
- Click Execute.
Swagger UI shows a 404 response. Its detail is Task not found.
Missing task still returns success?
Confirm that get_task reads from tasks instead of returning the path value. Check that the exception sits inside the if task is None block.
Refresh Swagger UI after saving the route. Help me diagnose the missing-task response.
The delete route can now reuse that protected lookup. A successful request removes the dictionary entry before returning its confirmation.
- Scroll below the replace_task route in main.py.
- Add the delete route by pasting this code:
@app.delete("/tasks/{task_id}")
def delete_task(task_id: int):
get_task(task_id)
del tasks[task_id]
return {"message": "Task deleted"}
What does the delete route do?
- get_task(task_id) stops the request when the task does not exist.
- del tasks[task_id] removes the matching entry from the in-memory dictionary.
- The returned message confirms successful deletion.
- Save main.py.
- Return to Swagger UI.
- Refresh the page.
- Use POST /tasks to create Archive completed report with priority low.
The response shows a new task with id set to 1.
- Expand DELETE /tasks/{task_id}.
- Click Try it out.
- Enter 1 for the task ID.
- Click Execute.
The response contains Task deleted. The task is no longer in the dictionary.
Deletion request returning 404?
Create a task again after every server reload because the dictionary starts empty. Use the ID returned by that creation request.
Confirm that delete_task calls get_task(task_id) before deleting the dictionary entry. Help me troubleshoot the delete route.
✔️ Awesome, I've got everything!
Great. Double-check that you saved main.py.
ⓧ I'd like to double check the full code
from typing import Literal
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
Priority = Literal["low", "medium", "high"]
app = FastAPI(title="Task Triage API")
class TaskCreate(BaseModel):
title: str
priority: Priority = "medium"
class Task(TaskCreate):
id: int
completed: bool = False
class TaskUpdate(TaskCreate):
completed: bool = False
tasks: dict[int, Task] = {}
next_id = 1
@app.get("/")
def read_root():
return {"message": "Task Triage API"}
@app.get("/tasks", response_model=list[Task])
def list_tasks():
return list(tasks.values())
@app.get("/tasks/{task_id}", response_model=Task)
def get_task(task_id: int):
task = tasks.get(task_id)
if task is None:
raise HTTPException(status_code=404, detail="Task not found")
return task
@app.post("/tasks", response_model=Task)
def create_task(task: TaskCreate):
global next_id
new_task = Task(
id=next_id,
title=task.title,
priority=task.priority,
)
tasks[next_id] = new_task
next_id += 1
return new_task
@app.put("/tasks/{task_id}", response_model=Task)
def replace_task(task_id: int, update: TaskUpdate):
get_task(task_id)
updated_task = Task(
id=task_id,
title=update.title,
priority=update.priority,
completed=update.completed,
)
tasks[task_id] = updated_task
return updated_task
@app.delete("/tasks/{task_id}")
def delete_task(task_id: int):
get_task(task_id)
del tasks[task_id]
return {"message": "Task deleted"}
Prove the full lifecycle
The complete demo follows one task through every route. Each response proves a different part of the API contract.
- Refresh Swagger UI.
- Create a task through POST /tasks with title Triage support queue and priority medium.
- Record the returned task ID: your generated task ID.
The creation response includes the generated ID plus the default incomplete state.
- Execute GET /tasks to list the queue.
- Retrieve task your generated task ID through GET /tasks/{task_id}.
The list contains your task. The single-task response returns the same typed fields.
- Replace task your generated task ID through PUT /tasks/{task_id} with title Resolve support queue.
- Set its priority to high.
- Set its completion state to true.
The replacement response keeps the generated ID. It shows the updated title plus priority plus completion state.
- Delete task your generated task ID through DELETE /tasks/{task_id}.
The response confirms Task deleted.
Before you retrieve that ID again, do you think the deleted resource will still produce a typed task?
- Retrieve task your generated task ID again through GET /tasks/{task_id}.
Swagger UI shows a 404 response with Task not found. The create-to-delete lifecycle now behaves consistently.
The dictionary stores tasks only inside the running Python process. Restarting that process reveals the storage limit.
- Create another task through POST /tasks with title Check restart behavior.
The response confirms that a task exists before the restart.
Before you restart the server, do you think that task will remain in the queue?
- Return to the integrated terminal from earlier.
- Press Ctrl+C to stop the development server.
- Press the Up Arrow key to recall the development command from earlier.
- Press Enter to restart the development server.
- Return to Swagger UI.
- Refresh the page.
- Execute GET /tasks.
The response is an empty list. Restarting the process reset the in-memory tasks dictionary.
- Create one more task through POST /tasks with title Confirm reset.
The new response uses id value 1. This proves that next_id also reset.
Server not restarting?
Confirm that the integrated terminal still belongs to the fastapi-task-api project. Check that the .venv environment remains activated.
Use the same development command that launched the server earlier. Help me restart my FastAPI development server.
That's the complete CRUD loop working. Your API now validates input plus replaces tasks plus deletes tasks plus reports missing resources.
Secret mission
Filter the Task Queue
Add independent completion and priority filters to the task list. You will narrow the queue from Swagger UI while preserving the route's unfiltered behavior.
Clean Up Your Resources
Clean Up Your Resources
Choose whether to keep the local API available, stop it for later, or remove the project from your computer. This FastAPI project runs entirely on your Windows machine, so leaving its files in place creates no ongoing cost.
Resources you used:
- A running local FastAPI development server in the integrated terminal from Visual Studio Code.
- The fastapi-task-api folder containing main.py plus requirements.txt.
- A project-local .venv folder containing FastAPI 0.156.1 plus Pydantic 2.14.1.
Keep everything running
No cleanup action is needed while you are actively testing the API. The local server uses your computer only while its development process is running.
- Leave the FastAPI development server running in the existing Visual Studio Code terminal.
- Keep the fastapi-task-api folder on your computer.
The Swagger UI remains available at http://127.0.0.1:8000/docs while the server runs.
Pause - I'll come back to this later
Pausing ends the local development process while preserving your source code. Your virtual environment stays ready for the next session.
- Return to the Visual Studio Code terminal running the FastAPI development server.
- Use the terminal's stop control to end the running process.
- Return to the Swagger UI tab in Microsoft Edge.
- Refresh the Swagger UI page.
The local address stops responding after the server ends. Your main.py file plus the .venv environment remain available inside fastapi-task-api.
Delete - I don't want to use this again
Deleting the folder permanently removes this project. Choose this option only if you no longer need the code.
- Return to the Visual Studio Code terminal running the FastAPI development server.
- Use the terminal's stop control to end the running process.
- Remove the fastapi-task-api folder from its parent directory by running these commands:
cd ..
rmdir /s /q fastapi-task-api
dir
What Do These Commands Remove?
- The first line moves the terminal into the folder that contains fastapi-task-api.
- The second line removes the complete project folder. This also deletes main.py, requirements.txt, and the .venv environment.
- The final line lists the remaining items. This provides the cleanup check.
The final listing should no longer include fastapi-task-api. Your local API files plus the installed project packages are now removed.
Folder Still Listed?
- Confirm that the FastAPI development server has stopped.
- Close any other terminal that is using the fastapi-task-api folder.
Help me remove my local FastAPI project folder.
Nice Work!
Nice Work!
You did it! Your FastAPI backend now manages a task queue through validated HTTP endpoints. Swagger UI gives you a browser-based control panel for demonstrating the complete workflow.
You've learned how to:
- Build a local Task Triage API. Demonstrate each endpoint through Swagger UI.
- Use typed Pydantic models to reject malformed input before route logic runs.
- Implement a complete CRUD workflow for tasks. Return clear 404 errors when a task does not exist.
- Secret Mission: Filter the task list by optional completion status. Narrow the same results by priority.
Ready to quiz yourself?