Fortune #
In this lab we will create a server for a fortune teller. We will delve deeper into design constraints and functionalities of an API.

[0] Set Up #
๐ป First, clone the repository
in your unit03_networking folder. Be sure to change yourgithubusername to your actual Github username.
cd ~/desktop/making_with_code/unit03_networking/
git clone https://github.com/the-isf-academy/lab_fortune_yourgithubusername
cd lab_fortuneyourgithubusername
poetry install
poetry shell
[1] Add new fortunes #
๐ Look at the definition of the SQL database table in fortunes.sql
Notice the data types of each column
CREATE TABLE IF NOT EXISTS fortunes (
id INTEGER PRIMARY KEY AUTOINCREMENT,
last_updated DATETIME DEFAULT (STRFTIME('%Y-%m-%d %H:%M:%S', 'now', '+8 hours')),
statement TEXT NOT NULL,
is_happy BOOLEAN NOT NULL,
num_updates INTEGER DEFAULT 0,
archive BOOLEAN DEFAULT 0
);
๐ป Open the database file to add a few fortunes: open database.db.
๐ป Add 3 new fortunes using the DB Browser, then save with โ+s. It’s important to have a variety of testing data to ensure the api works as expected.
- Notice how
booleansare represented as a0or a1
[2] Running the Fortune Server #
๐ป Start your local server.python api.py
๐ป Test the server using HTTPie: 127.0.0.1:5000/fortune
๐ Look at the JSON response in the help key and make requests to all of the endpoints.
{
"help": {
"GET /all": {
"description": "returns all fortunes",
"payload": ""
},
"POST /new": {
"description": "adds new fortune to database",
"payload": "statement: string, is_happy: boolean"
}
},
"overview": "This is the fortune server."
}
[3] API #
When building an API, it is important to consider how users will interact with it. It is the responsiblity of the developer to design an API with user interaction in mind.
We want users to be able to:
- filter by the
is_happycolumn - add on to each other’s fortunes
- delete fortunes if user has the admin key
๐ป Open the api.py file. This server has 2 endpoints:
/new/all
For each each feature, you will write a helper function to run the SQL commands and then write the endpoint.*
๐ป It is up to you to write the add the following features:
/is_happy- filters riddles byis_happypayload/update_statement- add to the end of an exisitng fortune/delete- delete a fortune with a specificidif thekeyis valid
/is_happy
#
๐ป In helpers.py write the get_all_ishappy() function. It should use SQL to fetch all riddles filtered on the is_happy parameter.
- parameter:
is_happy: boolean
โ CHECKPOINT:๐ป Test the function in bottom of
helpers.pyBe sure to test with
FalseandTrue. Look atdatabase.dbto confirm it is working.all_fortunes = get_all_ishappy(False) for fortune in all_fortunes: print(fortune['id'])
๐ป In api.py write the /is_happy endpoint. You should get_all_ishappy() function.
- HTTP method:
GET - Params/Payload:
is_happy
โ CHECKPOINT:๐ป Test the endpoint in the
HTTPie desktop apphttp://127.0.0.1:5000/fortune/is_happy is_happy=Trueโ๏ธ It should return
jsonlike:{ "fortunes": [ { "fortune": "you will win money everyday", "id": 1, "is_happy": true, "last_updated": "2025-09-15 04:06:14", "num_updates": 1 },
/update
#
๐ป In helpers.py write the update_fortune() function.
- parameter:
id: int, update_string: string - It should
- add
update_stringto the end of the existingstaetment - increase
num_updatesby 1 - updated
last_updatedto the current datetime - return the updated fortune
- add
โ CHECKPOINT:๐ป Test the function in bottom of
helpers.pyRefresh the
database.dbfile to confirm the fortune is properly updated.updated_fortune = update_fortune(1, 'money') print(updated_fortune['statement'], updated_fortune['num_updates'], updated_fortune['last_updated'] )
๐ป In api.py write the /update endpoint.
- HTTP method:
PUT - Payload/args:
id:integer,update_text:string - if the fortune exists
- call the
update_fortune()function
- call the
- else
- a helpful error message communicating the fortune does not exist
โ CHECKPOINT:๐ป Test the endpoint in the
HTTPie desktop apphttp://127.0.0.1:5000/fortune/update id=6 update_text=non-stopโ๏ธ It should return
jsonlike:{ "message": "Fortune updated successfully", "question": { "fortune": "tomorrow will rain non-stop", "id": 5, "is_happy": false, "last_updated": "2025-09-16 14:47:33", "num_updates": 2 } }
/search
#
๐ป In helpers.py write the serach() function.
- parameter:
keyword: string - return the fortunes from the database that contains the keyword
โ CHECKPOINT:๐ป Test the function in bottom of
helpers.pyall_fortunes = search("win") for fortune in all_fortunes: print(fortune['statement'])
๐ป In apy.py write the /search endpoint.
- HTTP method:
get - Payload/args:
keyword - It should return all fortunes that contain the keyword.
- If no fortunes exist, provide a helpful error message
๐ค Consider:
- Which existing endpoint is similar?
- How should you format the json that you return?
- What should you return to the user if no fortunes match their search term?
โ CHECKPOINT:๐ป Test the endpoint in the
HTTPie desktop apphttp://127.0.0.1:5000/fortune/search keyword="surprise"โ๏ธ It should return
jsonlike:{ "fortunes": [ { "id": 1, "statement": "A surprise pizza is coming", "num_updates": 1, "is_happy": true, "last_updated": "2025-09-15 14:06:14", }, { "id": 6, "statement": "A surprise typhoon day is in your future", "num_updates": 1, "is_happy": true, "last_updated": "2025-09-13 15:20:13", } ] }
API documentation
#
When designing an API, it is important to write helpful documentation so others can use it properly.
๐ป Look at the current help section by making a GET request to / endpoint in the HTTPie desktop app
{
"help": {
"GET /all": {
"description": "returns all fortunes",
"payload": ""
},
"GET /new": {
"description": "adds new fortune to database",
"payload": "statement: string, is_happy: boolean"
}
},
"overview": "This is the fortune server."
}
๐ป Add /is_happy, /update, and /search to the "help" section of the JSON.
โ CHECKPOINT:๐ป Test the endpoint in the
HTTPie desktop appโ๏ธ It should return
jsonlike:{ "help": { "GET /all": { "description": "returns all fortunes", "payload": "" }, "POST /new": { "description": "adds new fortune to database", "payload": "statement: string, is_happy: boolean" }, "GET /is_happy": { "description": "returns all fortunes filtered by is_happy", "payload": "is_happy: boolean" }, "PUT /update": { "description": "adds on to an existing fortune statement", "payload": "id: integer, update_text: string" }, "GET /search": { "description": "returns all fortunes filtered by a keyword in the statement", "payload": "keyword: string" } }, "overview": "This is the fortune server." }
[4] Deliverables #
โกโจOnce you’ve successfully completed the worksheet be sure to fill out this Google form.
๐ป Push your work to Github:
- git status
- git add -A
- git status
- git commit -m “describe your code and your process here”
be sure to customize this message, do not copy and paste this line
- git push
[5] Extensions #
Error Handling #
๐ป Try to break your server.
๐ป Now, add in proper error handling so the server never crashes, but instead provides helpful error messages.
Archive #
Currently there is no feature to delete a fortune. It can be risky to permanently delete an item from the database, so instead let’s utilize the archive column.
๐ป Write a method toggle_archive() that toggles the archive field to True or False, depending on its current state.
๐ป Write a new route /change_archive to change the archive field of a fortune with a given id.
๐ป Change your exisitng routes (/all, /search, and /all/is_happy) to only return fortunes with archive set to True.
Relational Database #
A relational database connects two databaes together with specific relationships. Learn more HERE.
Here are two example files for creating a many-to-one relationship between tutors and students. Each tutor, can have multiple students.
๐ Example database.sql file. Notice how the student is linked to the tutor by the FOREIGN KEY.
CREATE TABLE tutor (
tutor_id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL
);
CREATE TABLE student (
student_id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
tutor_id INTEGER,
FOREIGN KEY (tutor_id) REFERENCES tutor(tutor_id)
);
๐ Example init_db.py file. Notice how when a student is created it is explictly linked to a tutor via the tutor_id.
import sqlite3
# connect to database
connection = sqlite3.connect('database.db')
# open database.sql
with open('database.sql') as file:
connection.executescript(file.read())
# create connection to database
conn = connection.cursor()
# inserts new tutor
conn.execute("INSERT INTO tutor (name) VALUES (?)", ("Emma",))
# inserts 2 students
conn.execute("INSERT INTO student (name, tutor_id) VALUES (?, ?)", ("Kris", 1))
conn.execute("INSERT INTO student (name, tutor_id) VALUES (?, ?)", ("Heidi", 1))
# save changes to db
connection.commit()
# close database
connection.close()
print("-- database initalized --")
๐ป Incorporate a many-to-one relational database in the fortune lab. Consider what relationship would make sense in the context of the feature of this API.