Backend Solutions


Knowledge Check Answers

  1. jsonify() converts a Python dictionary to a JSON response and sets the correct Content-Type: application/json header. Without it, Flask may return unexpected formats or errors.

  2. A virtual environment isolates your project's dependencies from the system Python, preventing version conflicts between projects.

  3. db.session.commit() saves all pending changes to the database. Without it, changes (adds, updates, deletes) are lost when the session ends.

  4. nullable=False means the column cannot be NULL/empty in the database. An error is raised if you try to save a record without providing a value.

  5. .env files contain secrets (API keys, passwords). If committed to git and pushed to a public repository, anyone can see and steal your keys.


Challenge 1 Solution

Add to routes.py:

@api.route("/messages", methods=["DELETE"])
def delete_all_messages():
    count = Message.query.delete()
    db.session.commit()
    return jsonify({"deleted": count})

Test:

curl -X DELETE http://localhost:5000/api/messages
# → {"deleted": 5}

Challenge 2 Solution

In-memory approach (simple):

Add near the top of routes.py:

CURRENT_SYSTEM_PROMPT = "You are a helpful assistant."

Add two new routes:

@api.route("/system-prompt", methods=["GET"])
def get_system_prompt():
    global CURRENT_SYSTEM_PROMPT
    return jsonify({"current_prompt": CURRENT_SYSTEM_PROMPT})


@api.route("/system-prompt", methods=["POST"])
def set_system_prompt():
    global CURRENT_SYSTEM_PROMPT
    data = request.get_json()
    CURRENT_SYSTEM_PROMPT = data.get("prompt", CURRENT_SYSTEM_PROMPT)
    return jsonify({"current_prompt": CURRENT_SYSTEM_PROMPT})

Then update the chat route to use CURRENT_SYSTEM_PROMPT:

messages = [
    {"role": "system", "content": CURRENT_SYSTEM_PROMPT},
    {"role": "user", "content": user_message},
]

Test:

curl -X POST http://localhost:5000/api/system-prompt \
  -H "Content-Type: application/json" \
  -d '{"prompt": "You speak like a pirate."}'

curl -X POST http://localhost:5000/api/chat \
  -H "Content-Type: application/json" \
  -d '{"message": "Hello!"}'
# → "Ahoy, matey!"

Challenge 3 Solution

Modify the chat route to include conversation history:

@api.route("/chat", methods=["POST"])
def chat():
    data = request.get_json()
    user_message = data.get("message", "")

    if not user_message:
        return jsonify({"error": "Message is required"}), 400

    user_msg = Message(content=user_message, sender="user")
    db.session.add(user_msg)
    db.session.commit()

    try:
        # Get last 10 messages for context
        recent = Message.query.order_by(Message.timestamp.desc()).limit(10).all()
        recent.reverse()

        messages = [{"role": "system", "content": "You are a helpful assistant."}]
        for msg in recent:
            role = "user" if msg.sender == "user" else "assistant"
            messages.append({"role": role, "content": msg.content})

        response = client.chat.completions.create(
            model="gpt-4o-mini",
            messages=messages,
        )

        ai_reply = response.choices[0].message.content
        ai_msg = Message(content=ai_reply, sender="ai")
        db.session.add(ai_msg)
        db.session.commit()

        return jsonify({
            "user_message": user_msg.to_dict(),
            "ai_message": ai_msg.to_dict(),
        })

    except Exception as e:
        return jsonify({"error": str(e)}), 500

Open-Ended Project: Todo List API

models.py — add Todo model

class Todo(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String(200), nullable=False)
    completed = db.Column(db.Boolean, default=False)
    created_at = db.Column(db.DateTime, default=lambda: datetime.now(timezone.utc))

    def to_dict(self):
        return {
            "id": self.id,
            "title": self.title,
            "completed": self.completed,
            "created_at": self.created_at.isoformat() if self.created_at else None,
        }

routes.py — add todo routes

@api.route("/todos", methods=["GET"])
def get_todos():
    completed = request.args.get("completed")
    sort = request.args.get("sort", "created_at")
    order = request.args.get("order", "desc")

    query = Todo.query
    if completed is not None:
        val = completed.lower() == "true"
        query = query.filter_by(completed=val)

    if order == "desc":
        query = query.order_by(getattr(Todo, sort).desc())
    else:
        query = query.order_by(getattr(Todo, sort).asc())

    todos = query.all()
    return jsonify([t.to_dict() for t in todos])


@api.route("/todos", methods=["POST"])
def create_todo():
    data = request.get_json()
    if not data or not data.get("title"):
        return jsonify({"error": "Title is required"}), 400

    todo = Todo(title=data["title"])
    db.session.add(todo)
    db.session.commit()
    return jsonify(todo.to_dict()), 201


@api.route("/todos/<int:todo_id>", methods=["PUT"])
def update_todo(todo_id):
    todo = Todo.query.get(todo_id)
    if not todo:
        return jsonify({"error": "Not found"}), 404

    data = request.get_json()
    if "completed" in data:
        todo.completed = data["completed"]
    if "title" in data:
        todo.title = data["title"]
    db.session.commit()
    return jsonify(todo.to_dict())


@api.route("/todos/<int:todo_id>", methods=["DELETE"])
def delete_todo(todo_id):
    todo = Todo.query.get(todo_id)
    if not todo:
        return jsonify({"error": "Not found"}), 404
    db.session.delete(todo)
    db.session.commit()
    return jsonify({"deleted": True})

Test the full flow:

# Create
curl -X POST http://localhost:5000/api/todos \
  -H "Content-Type: application/json" \
  -d '{"title": "Learn Flask"}'

# List
curl http://localhost:5000/api/todos

# Complete
curl -X PUT http://localhost:5000/api/todos/1 \
  -H "Content-Type: application/json" \
  -d '{"completed": true}'

# Delete
curl -X DELETE http://localhost:5000/api/todos/1