INFO 153B/253B: Backend Web Architecture
Week 3
Kay Ashaolu - Instructor
Suk Min Hwang - GSI
Quick check-in on O'Reilly Chapter 3
pip install flask, create app.pyapp = Flask(__name__)@app.get('/store')flask runfrom flask import Flask
app = Flask(__name__)
@app.get('/store')
def get_stores():
return {"stores": []}
if __name__ == '__main__':
app.run(debug=True)
Flask(__name__) creates the application instance@app.get is a decorator - routes URL to functiondebug=True enables auto-reload and better error messages@app.get('/store')
def get_stores():
stores = [
{"name": "My Store", "items": [{"name": "Chair", "price": 15.99}]}
]
return {"stores": stores}
Content-Type: application/json headerTrue becomes true, None becomes nulljson.dumps() manually# GET request
curl http://localhost:5000/store
# POST request with JSON data
curl -X POST http://localhost:5000/store \
-H "Content-Type: application/json" \
-d '{"name": "New Store"}'
curl is a command-line HTTP client-X POST specifies the HTTP method-H adds headers (Content-Type tells Flask it's JSON)-d sends data in the request body@app.get('/path')(data, status_code)?
Flask routes ARE those functions - just with a decorator on top.
The decorator is the only difference
# Week 2: Pure Python function
def get_student(student_id):
"""Get a student by ID."""
student = students.get(student_id)
if student is None:
return {"error": "Student not found"}, 404
return student, 200
# Testing it
result, status = get_student(1)
print(f"Status: {status}")
print(result)
(data, status_code)def get_student(student_id):
student = students.get(student_id)
if student is None:
return {"error": "Not found"}, 404
return student, 200
@app.get('/students/<int:student_id>')
def get_student(student_id):
student = students.get(student_id)
if student is None:
return {"error": "Not found"}, 404
return student, 200
@app.get('/students/<int:student_id>')
def get_student(student_id):
...
@app.get - Listen for GET requests/students/ - At this URL path<int:student_id> - Capture an integer from the URLGET /students/42get_student(student_id=42)
@app.get('/students')
def list_students():
return {"students": list(students.values()), "total": len(students)}
@app.get('/students/<int:student_id>')
def get_student(student_id):
# Same logic as Week 2
@app.post('/students')
def create_student():
# Same logic as Week 2's add_student
@app.put('/students/<int:student_id>')
def update_student(student_id):
# Same logic as Week 2
@app.delete('/students/<int:student_id>')
def delete_student(student_id):
# Same logic as Week 2
Designing RESTful APIs
| Method | Action | Example | Status Code |
|---|---|---|---|
| GET | Read/Retrieve | GET /students/1 | 200 OK |
| POST | Create | POST /students | 201 Created |
| PUT | Replace entirely | PUT /students/1 | 200 OK |
| PATCH | Update partially | PATCH /students/1 | 200 OK |
| DELETE | Remove | DELETE /students/1 | 200 OK or 204 |
GET /getStudents
POST /createStudent
GET /deleteStudent/1
POST /updateStudent/1
GET /students
POST /students
DELETE /students/1
PUT /students/1
/students not /student# Students collection
GET /students # List all students
POST /students # Create a student
# Single student resource
GET /students/1 # Get student with ID 1
PUT /students/1 # Update student with ID 1
DELETE /students/1 # Delete student with ID 1
# Nested resources (if needed)
GET /students/1/courses # Get courses for student 1
POST /students/1/courses # Add a course to student 1
/students/students/{id}/students/{id}/courses| Code | Name | When to Use |
|---|---|---|
| 200 | OK | Successful GET, PUT, PATCH, DELETE |
| 201 | Created | Successful POST (resource created) |
| 204 | No Content | Successful DELETE (nothing to return) |
| 400 | Bad Request | Invalid data from client |
| 404 | Not Found | Resource doesn't exist |
| 500 | Server Error | Something broke on your end |
from flask import Flask, request
app = Flask(__name__)
students = {} # In-memory storage
@app.get('/students')
def list_students():
return {"students": list(students.values())}
@app.post('/students')
def create_student():
data = request.get_json()
student = {"id": len(students) + 1, **data}
students[student["id"]] = student
return student, 201 # 201 = Created
@app.get('/students/<int:id>')
def get_student(id):
if id not in students:
return {"error": "Student not found"}, 404
return students[id]
Handling data in Flask
| Source | Example | Flask Access |
|---|---|---|
| URL Path | /students/42 | Function parameter |
| Query String | ?major=CS&limit=10 | request.args.get() |
| Request Body | JSON payload | request.get_json() |
| Headers | Authorization: Bearer xyz | request.headers.get() |
@app.post('/students')
def create_student():
# Get JSON data from request body
data = request.get_json()
# data is now a Python dictionary
name = data["name"]
email = data["email"]
# Create the student...
return new_student, 201
request.get_json() parses JSON body into a dictContent-Type: application/json headerNone if no JSON or invalid JSON@app.get('/students')
def list_students():
# Get optional query parameters
major = request.args.get('major') # ?major=CS
limit = request.args.get('limit', default=10, type=int)
results = list(students.values())
# Filter by major if provided
if major:
results = [s for s in results if s.get("major") == major]
# Apply limit
results = results[:limit]
return {"students": results, "total": len(results)}
request.args.get('key') returns query parameter or Nonedefault= provides fallback valuetype=int converts string to integer# Integer parameter
@app.get('/students/<int:student_id>')
def get_student(student_id):
# student_id is already an int
...
# String parameter (default)
@app.get('/users/<username>')
def get_user(username):
# username is a string
...
# Multiple parameters
@app.get('/courses/<course_code>/students/<int:student_id>')
def get_enrollment(course_code, student_id):
...
<name> captures string (default)<int:name> captures and validates as integer# Simple: just return a dict (200 assumed)
return {"name": "Alice"}
# With status code
return {"name": "Alice"}, 200
# With status code (error)
return {"error": "Not found"}, 404
# With headers
return {"name": "Alice"}, 200, {"X-Custom-Header": "value"}
# Using jsonify (explicit)
from flask import jsonify
return jsonify({"name": "Alice"}), 200
(data, status_code)Building robust APIs
@app.get('/students/<int:student_id>')
def get_student(student_id):
student = students.get(student_id)
if student is None:
return {"error": "Student not found"}, 404
return student, 200
from flask import abort
@app.get('/students/<int:student_id>')
def get_student(student_id):
student = students.get(student_id)
if student is None:
abort(404) # Immediately returns 404 response
return student
abort(status_code) immediately stops and returns error@app.errorhandler(404)
def not_found(error):
return {"error": "Resource not found"}, 404
@app.errorhandler(400)
def bad_request(error):
return {"error": "Bad request"}, 400
@app.errorhandler(500)
def server_error(error):
return {"error": "Internal server error"}, 500
@app.errorhandler(code) handles that status codeabort(404) returns JSON, not HTML@app.post('/students')
def create_student():
data = request.get_json()
# Validate required fields
if not data:
return {"error": "No JSON data provided"}, 400
if "name" not in data:
return {"error": "Missing required field: name"}, 400
if "email" not in data:
return {"error": "Missing required field: email"}, 400
# Validate email format (simple check)
if "@" not in data["email"]:
return {"error": "Invalid email format"}, 400
# Now safe to create student
new_student = {"id": next_id, **data}
students[next_id] = new_student
return new_student, 201
@app.post('/students')
def create_student():
try:
data = request.get_json()
# ... validation and processing
return new_student, 201
except KeyError as e:
return {"error": f"Missing field: {e}"}, 400
except Exception as e:
# Log the error for debugging
app.logger.error(f"Error creating student: {e}")
return {"error": "Internal server error"}, 500
Key takeaways from today
| Week | Topic | Building On |
|---|---|---|
| 4 | Docker | Containerize your Flask app |
| 6 | Validation & Schemas | Flask-Smorest for data validation |
| 7 | SQLAlchemy + Migrations | Replace in-memory with PostgreSQL |
| 8 | Async Task Queues | Background processing with Redis |
From Week 2 Function to Flask API
get_student() functionflask runStudent Records Flask API
GET /students - List allGET /students/<id> - Get onePOST /students - CreatePUT /students/<id> - UpdateDELETE /students/<id> - Deletegit clone <your-repo-url>
cd in-class-exploration-week-3-<your-username>
python3 -m venv venv
source venv/bin/activate # On Windows: venv\Scripts\activate
pip install -r requirements.txt
flask run
# List all students
curl http://localhost:5000/students
# Get one student
curl http://localhost:5000/students/1
# Create a student
curl -X POST http://localhost:5000/students \
-H "Content-Type: application/json" \
-d '{"name": "New Student", "email": "new@berkeley.edu", "major": "CS"}'
# Update a student
curl -X PUT http://localhost:5000/students/1 \
-H "Content-Type: application/json" \
-d '{"name": "Updated Name"}'
# Delete a student
curl -X DELETE http://localhost:5000/students/1
git add .
git commit -m "In-class exploration submission"
git push
Want to keep working? Continue after submitting - just push additional changes.
Website: groups.ischool.berkeley.edu/i253/sp26
Email: kay@ischool.berkeley.edu