Backend Step 4: API Endpoints

You'll create the actual chat API endpoints that the mobile app will call.

What You'll Learn

  • POST and GET endpoints
  • Reading request data with request.get_json()
  • Saving and retrieving data from the database
  • Using Blueprints to organize routes

Step 1: Create a Blueprint

A Blueprint lets you define routes in a separate file. Create backend/app/routes.py:

from flask import Blueprint, request, jsonify
from . import db
from .models import Message

api = Blueprint("api", __name__)


@api.route("/status")
def status():
    return jsonify({"status": "ok"})


@api.route("/messages", methods=["GET"])
def get_messages():
    messages = Message.query.order_by(Message.timestamp.asc()).all()
    return jsonify([msg.to_dict() for msg in messages])


@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()

    # For now, return a hardcoded reply
    ai_reply = f"You said: '{user_message}'. I'm a basic bot for now!"
    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(),
    })

Step 2: Organize Tests with conftest.py

When you have multiple test files, they often share the same setup code. Instead of repeating it, use a conftest.py file that pytest loads automatically.

Create backend/tests/conftest.py:

import pytest
from app import create_app, db as _db


@pytest.fixture
def app():
    app = create_app()
    app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///:memory:"
    app.config["TESTING"] = True
    return app


@pytest.fixture
def client(app):
    return app.test_client()


@pytest.fixture(autouse=True)
def db(app):
    with app.app_context():
        _db.create_all()
        yield _db
        _db.session.rollback()
        _db.drop_all()

This file: - Creates a Flask app in memory (no files, no leftover data) - Provides a client fixture that your tests use - Sets up and tears down the database before and after every test - The autouse=True means it runs automatically — you don't need to mention it

Now any test file in tests/ can use client and app without any setup code.

Step 3: Update init.py to Use the Blueprint

from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from dotenv import load_dotenv

load_dotenv()

db = SQLAlchemy()


def create_app():
    app = Flask(__name__)
    app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///chat.db"
    app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False

    db.init_app(app)

    from .routes import api
    app.register_blueprint(api, url_prefix="/api")

    with app.app_context():
        db.create_all()

    return app


if __name__ == "__main__":
    app = create_app()
    app.run(debug=True, port=5000)

Step 4: Write Automated Tests for Endpoints

Create backend/tests/test_chat.py:

def test_chat_with_message_returns_two_messages(client):
    response = client.post("/api/chat", json={"message": "Hello"})
    assert response.status_code == 200
    data = response.get_json()
    assert "user_message" in data
    assert "ai_message" in data
    assert data["user_message"]["sender"] == "user"
    assert data["ai_message"]["sender"] == "ai"


def test_chat_empty_message_returns_400(client):
    response = client.post("/api/chat", json={})
    assert response.status_code == 400
    assert "error" in response.get_json()

Create backend/tests/test_messages.py:

def test_get_messages_returns_list(client):
    response = client.get("/api/messages")
    assert response.status_code == 200
    assert isinstance(response.get_json(), list)


def test_delete_nonexistent_message_returns_404(client):
    response = client.delete("/api/messages/999")
    assert response.status_code == 404


def test_delete_message_works(client):
    client.post("/api/chat", json={"message": "Test"})
    messages = client.get("/api/messages").get_json()
    msg_id = messages[0]["id"]

    response = client.delete(f"/api/messages/{msg_id}")
    assert response.status_code == 200
    assert response.get_json()["deleted"] is True

Run all tests:

cd aiproject/backend
python -m pytest tests/ -v

Expected output (5 tests):

tests/test_chat.py::test_chat_with_message_returns_two_messages PASSED
tests/test_chat.py::test_chat_empty_message_returns_400 PASSED
tests/test_messages.py::test_get_messages_returns_list PASSED
tests/test_messages.py::test_delete_nonexistent_message_returns_404 PASSED
tests/test_messages.py::test_delete_message_works PASSED

What these tests cover: - Happy path: sending a message works correctly - Edge case: empty message returns error - GET endpoint: returns a list - DELETE: successful and not-found cases

This is called boundary testing — testing both the happy path and the error cases.

Step 5: Test the Endpoints (Manual)

Test GET /api/status

curl http://127.0.0.1:5000/api/status
# → {"status":"ok"}

Test POST /api/chat

curl -X POST http://127.0.0.1:5000/api/chat \
  -H "Content-Type: application/json" \
  -d '{"message": "Hello!"}'

Expected response:

{
  "user_message": {"id": 1, "content": "Hello!", "sender": "user", "timestamp": "..."},
  "ai_message": {"id": 2, "content": "You said: 'Hello!'. I'm a basic bot for now!", "sender": "ai", "timestamp": "..."}
}

Test GET /api/messages

curl http://127.0.0.1:5000/api/messages

Expected response — list of all messages so far.

Test POST with empty message

curl -X POST http://127.0.0.1:5000/api/chat \
  -H "Content-Type: application/json" \
  -d '{}'

Expected: {"error":"Message is required"} with status 400.

Step 6: How It Works

POST /api/chat flow: 1. Mobile app sends JSON {"message": "Hello!"} 2. Flask receives the request 3. request.get_json() parses the JSON body 4. Creates a new Message with sender="user" and saves to database 5. Generates a placeholder AI reply 6. Saves the AI reply to database 7. Returns both messages as JSON

GET /api/messages flow: 1. Mobile app requests all messages 2. Flask queries the database, ordered by timestamp 3. Converts each Message to a dict via to_dict() 4. Returns the list as JSON

Step 7: Understanding HTTP Methods

Method Endpoint What it does
GET /api/messages Read all messages
POST /api/chat Send a message, get reply

POST is used when creating data. GET is used when reading data.

Step 8: Verification

Run all three tests above and confirm you get JSON responses. If POST returns a 400 error, check that you included the -H "Content-Type: application/json" header.