See the README file for the requirements.
-
Set up your FastAPI by creating your
main.pyfile.Tip: You can start with something simple, such as:
# main.py from fastapi import FastAPI app = FastAPI() @app.get("/") def read_root(): return {"message": "This is YOUR API!"}
-
From the terminal, in the same folder where
main.pyis located, run the following command:uvicorn main:app --reload
If the script was successful, you can try opening your browser to:
-
http://localhost:8000 - shows the welcome message.
-
http://localhost:8000/docs - shows your API in Swagger UI
-
http://localhost:8000/redoc - shows your API in Redoc UI
Troubleshooting: To ensure that the the
uvicorncommand runs successfully, make sure you are using a virtual environment (venv). -
-
In your
main.pyfile, add to the top, below the FastAPI import:from pydantic import BaseModel, Field, HttpUrl from typing import Optional from datetime import date
-
Define the
ForageItemmodel, by creating a new class:class ForageItem(BaseModel): id_num: int = Field(..., example=1, description="Unique ID for the item") name: str = Field(..., example="Chanterelle", description="Name of the mushroom or berry") item_type: str = Field(..., example="mushroom", description="Either 'mushroom' or 'berry'") location: str = Field(..., example="Grimsta Naturreservat", description="Where it was found") date_found: date = Field(..., example="2025-07-21", description="Date of the foraging event") is_edible: bool = Field(..., example=True, description="Whether the item is safe to eat") notes: Optional[str] = Field(None, example="Found under pine trees", description="Extra notes") photo_url: Optional[HttpUrl] = Field(None, example="https://example.com/photo.jpg" description="Optional image link")
Explanation of the Python constructs used:
-
BaseModela class that is the "core" of Pydandic. All your data models inherit from it. -
Field()a function that lets your customise descriptions, examples, and validation. -
HttpUrla type that ensures only valid URLs are provided for image links. -
Optional[...]a type that lets you declare if a field is optional. -
datea class allows you to store calendar dates, as adatedata type, not just as a string (str). FastAPI will automatically convert string input into adatetype if the format is valid.
-
-
Run the API with the following command:
uvicorn main:app --reload
-
Visit the Swagger or Redoc version of the API to see how it has been rendered.
-
Create an empty list to act as a "fake" database, after
app=FastAPI():fake_db = []
This list will hold each
ForageItemthat youPOST. It acts as a temporary storage. You can later replace this with a real database, such as SQLite. -
Define
POST /itemsroute:a. Add the following to your imports:
from fastapi import HTTPException
b. Update the
@app.getto@app.post.c. In
@app.post, add the following:@app.post("/items", response_model=ForageItem, status_code=201) ...Explantion of elements used:
-
This creates a
POSTendpoint at/items -
response_model=ForageItemstells FastAPI to use the same schema when sending the response -
status_code=201is the HTTP code for "Created"
-
-
Use the
ForageItemmodel to validate incoming data, by adding the following in the next line after@app.post():def create_item(item: ForageItem): if any(existing.id == item.id for existing in fake_db): raise HTTPException(status_code=400, detail="Item with this ID already exists.")
-
Add the new item to the list and return it:
fake_db.append(item) return item
-
Go to http://localhost:8000/docs, click POST /items, and try the following sample payload:
{ "id": 1, "name": "Chanterelle", "type": "mushroom", "location": "Tyresta National Park", "date": "2025-07-21", "is_edible": true, "notes": "Found under pine trees", "photo_url": "https://example.com/chanterelle.jpg" }You should get a
201 Createdresponse and see your item echoed back.
Full code added from this section:
# main.py
from fastapi import FastAPI
from fastapi import HTTPException
from pydantic import BaseModel, Field, HttpUrl
from typing import Optional
from datetime import date
# Create an instance of the FastAPI application
app = FastAPI()
# Temporary in-memory storage for forage items
fake_db = []
# Define a new data model called ForageItem, which inherits from BaseModel, so FastAPI knows how to parse, validate, and document it
class ForageItem(BaseModel):
id_num: int = Field(..., example=1, description="Unique ID for the item")
name: str = Field(..., example="Chanterelle", description="Name of the mushroom or berry")
item_type: str = Field(..., example="mushroom", description="Either 'mushroom' or 'berry'")
location: str = Field(..., example="Grimsta Naturreservat", description="Where it was found")
date_found: date = Field(..., example="2025-07-21", description="Date of the foraging event")
is_edible: bool = Field(..., example=True, description="Whether the item is safe to eat")
notes: Optional[str] = Field(None, example="Found under pine trees", description="Extra notes")
photo_url: Optional[HttpUrl] = Field(None, example="https://example.com/photo.jpg", description="Optional image link")
# Define a route: when someone visits GET / (the root), this function runs
@app.post("/items", response_model=ForageItem, status_code=201)
def create_item(item: ForageItem):
# Check for duplicates by ID
if any(existing.id == item.id for existing in fake_db):
raise HTTPException(status_code=400, detail="Item with this ID already exists.")
# Add the item to the fake database
fake_db.append(item)
return itemNow that the API allows you to create (but not yet store) items, add to the script to be able to retrieve items.
-
Add the following to
main.py:- Import
typingfromList - Add
@app.getand the following function below the existingPOSTroute
from typing import List ... # Rest of the script @app.get("/items", response_model=List[ForageItem]) def get_all_items(): return fake_db
Explanation of what was added:
For
@app.get,-
The function defines a GET endpoint at
/items. -
The
response_modeldeclares that it will return a list ofForageItems.
- Import
-
(Optional) Test out the
GETfunction:-
Run the API from your CLI.
-
Go to http://127.0.0.1:8000/docs (features Try it out).
-
In the
POSTsection, click Try it out and paste in the example value. -
Click Execute. (Tip: try adding another example response 😄.)
-
Go to the
GETsection, and click Try it out.
You should see the example(s) values you executed in the
POSTsection. -
To retrieve foraged items by its unique ID, add GET /items/{id} by adding the following to main.py:
from fastapi import Path
# Add this below the other routes
@app.get("/items/{item_id}", resposne_model=ForageItem)
def get_item_by_id(item_id: int = Path(..., description="ID of the item you want to retrieve")):
# Search the fake database for the item
for item in fake_db:
if item.id == item_id:
return item
# If not found, raise a 404 error
raise HTTPException(status_code=404, detail="Item not found. Try another ID number.")Note: Path(...) is optional but useful for docs (you can add more metadata or validations here). It is a function from FastAPI that lets you describe and validate path parameters. It allows you to:
-
Set constraints or rules
-
Declare default values if the parameter is not required
-
Add more descriptive information for documentation (for example, with Redocly or Swagger)
(With DELETE /items/{id})
(With PUT /items/{id})
