In this lab, you'll learn the essentials of REST APIs, HTTP request methods, and how to build a simple API using Python's standard library. You'll also practice returning proper response codes and JSON data. The challenge will have you build a CRUD API using real networking data from psutil.
A REST API (Representational State Transfer) is a way for programs to communicate over HTTP using standard methods and resource-based URLs. REST APIs are stateless and use standard HTTP verbs to perform actions on resources.
| Method | Purpose |
|---|---|
| GET | Retrieve data |
| POST | Create new data |
| PUT | Update/replace data |
| DELETE | Remove data |
Other methods: PATCH (partial update), OPTIONS (capabilities)
In this lab, you'll learn how to build a REST API using Flask, a popular Python web framework. You'll focus on Flask module usage, reading parameters, returning JSON, and using Flask's documentation tools.
- Flask makes it easy to define routes, handle HTTP methods, and return JSON.
- You can read the Flask docs with:
python -m pydoc flaskpython -m pydoc flask.apppython -m pydoc flask.Flaskpython -m pydoc flask.Flask.getpython -m pydoc flask.Flask.postpython -m pydoc flask.Flask.route
- Flask's official docs: https://flask.palletsprojects.com/
This is not a real database! All data is lost when the server restarts.
"""
User API Example (Flask)
# Create a new user
curl -X POST -H "Content-Type: application/json" -d '{"username": "alice", "role": "user"}' http://localhost:8000/newuser
# Get user details by username
curl http://localhost:8000/users/alice
# Update username by id
curl -X PUT -H "Content-Type: application/json" -d '{"username": "alice2"}' http://localhost:8000/users/1
# Delete user by id
curl -X DELETE http://localhost:8000/users/1
# Get all users
curl http://localhost:8000/users
# Get user details by username (again)
curl http://localhost:8000/users/alice
# Note: Both GET endpoints above can also be accessed directly in your browser by navigating to:
# http://localhost:8000/users
# http://localhost:8000/users/alice
# You can replace localhost with public ip
# if port and firewall is open
"""
from flask import Flask, request, jsonify, abort
app = Flask(__name__)
# In-memory user database (lost on restart)
user_database = {
1: {"id": 1, "username": "admin", "role": "admin"},
2: {"id": 2, "username": "bob", "role": "user"}
}
next_id = 3
@app.route('/users/<username>', methods=['GET'])
def get_user(username):
"""Get user details by username"""
for user in user_database.values():
if user["username"] == username:
return jsonify(user)
return jsonify({"error": "User not found"}), 404
@app.get('/users')
def get_users():
"""Get all users"""
return jsonify(user_database)
@app.route('/newuser', methods=['POST'])
def create_user():
"""Create a new user"""
global next_id
data = request.get_json()
if not data or "username" not in data:
return jsonify({"error": "Missing username"}), 400
user = {"id": next_id, "username": data["username"], "role": data.get("role", "user")}
user_database[next_id] = user
next_id += 1
return jsonify(user), 201
@app.route('/users/<int:user_id>', methods=['PUT'])
def update_user(user_id):
"""Update a user's username by id"""
user = user_database.get(user_id)
if not user:
return jsonify({"error": "User not found"}), 404
data = request.get_json()
if not data or "username" not in data:
return jsonify({"error": "Missing username"}), 400
user["username"] = data["username"]
return jsonify(user)
@app.route('/users/<int:user_id>', methods=['DELETE'])
def delete_user(user_id):
"""Delete a user by id (forbid deleting admin)"""
user = user_database.get(user_id)
if not user:
return jsonify({"error": "User not found"}), 404
if user["role"] == "admin":
return jsonify({"error": "Cannot delete admin user"}), 403
del user_database[user_id]
return '', 204
@app.errorhandler(500)
def internal_error(error):
return jsonify({"error": "Internal server error"}), 500
if __name__ == '__main__':
app.run(debug=True, host='0.0.0.0', port=8000)Use request.args to access query parameters:
@app.route('/search')
def search():
term = request.args.get('q', '')
return jsonify({'results': [], 'query': term})Access headers with request.headers:
@app.route('/whoami')
def whoami():
agent = request.headers.get('User-Agent', 'unknown')
return jsonify({'user_agent': agent})Use make_response() for advanced control:
from flask import make_response
@app.route('/custom')
def custom():
resp = make_response(jsonify({'msg': 'hi'}), 202)
resp.headers['X-Custom'] = 'value'
return respCustom error handlers:
@app.errorhandler(404)
def not_found(e):
return jsonify({'error': 'Not found'}), 404
@app.errorhandler(400)
def bad_request(e):
return jsonify({'error': 'Bad request'}), 400Use abort() to raise errors:
from flask import abort
@app.route('/fail')
def fail():
abort(400, description='You failed!')from flask import Blueprint
user_bp = Blueprint('user', __name__)
@user_bp.route('/users')
def list_users():
return jsonify(list(user_database.values()))
app.register_blueprint(user_bp)@app.before_request
def log_request():
print(f"{request.method} {request.path}")
@app.after_request
def add_header(response):
response.headers['X-Processed-By'] = 'Flask'
return responseapp.config['MY_SETTING'] = 'value'
import os
app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY', 'dev')-
Use
@app.get,@app.post, etc. for concise HTTP method routing. -
@app.getis same as using@app.route(..., methods=['GET']) -
For cross-origin requests (CORS), add:
from flask_cors import CORS CORS(app)
-
CORS is needed when your frontend and backend run on different domains/ports, allowing browsers to access your API.
- Run with
flask run(setFLASK_APP=yourfile.py) - Use
flask shellfor an interactive shell with app context
- Use
python -m pydoc flask,python -m pydoc flask.app, etc. - Flask’s official docs: https://flask.palletsprojects.com/
Goal: Build a REST API that exposes networking data (routes, interfaces, MAC addresses, etc.) using psutil. All data should be loaded into a dictionary and modified in-memory only.
Requirements:
- Use
psutilto gather networking data (e.g., interfaces, MACs, routes) - Store this data in a dictionary (in-memory)
- Implement CRUD operations:
- GET: Return current data
- POST: Add new entry
- PUT: Update entry
- DELETE: Remove entry
- Use Flask for all endpoints
- Return proper status codes and JSON responses
- Add a
/searchendpoint that takes query parameters and returns any dictionary entries (from any resource) where any key or value matches the search term (case-insensitive). - For every request, log the User-Agent and client IP address (use
request.headersandrequest.remote_addr). - Use
@app.before_requestand@app.after_requestto log the request method, path, and the time taken to complete each request (start timer in before, log duration in after).
Bonus:
- Support response in JSON, XML, or YAML (based on Accept header or query param)
Tips:
- Use
request.get_json()to read request bodies (for POST/PUT) - Use
request.argsto read query parameters - Use
jsonify()to return JSON
- Use
http.server(subclassBaseHTTPRequestHandler) - Return proper status codes and JSON responses
- Support response in JSON, XML, or YAML (based on Accept header or query param)
GET /interfaces→ List all interfacesGET /macs→ List all MAC addressesGET /routes→ List all routes (simulate if needed)POST /routes→ Add a route (in-memory only)PUT /routes/<id>→ Update a routeDELETE /routes/<id>→ Delete a route
- Use
self.rfile.read()to read request bodies (for POST/PUT) - Use
json.loads()to parse JSON input - Use
self.pathto determine the endpoint - Use
self.headersto check forAccepttype (for bonus)
If you finish early, try building a similar API using FastAPI. FastAPI is similar to Flask but uses async programming, so it can process multiple requests at the same time. You don’t need to learn all the details—just refer to the official docs and try making another implementation of your API with FastAPI for comparison.