Implementing API Endpoints

Introduction: The Implementation Phase

Welcome back. In the previous lessons, we laid a solid foundation for our Movie Recommendation System. We started by designing our API structure, defining exactly what our endpoints should look like. Then, we adopted a Test-Driven Development (TDD) approach, writing a suite of tests that currently fail because we haven't written the actual application logic yet.

Now, we enter the implementation phase. Our goal is to turn those failing "red" tests into passing "green" ones. However, instead of writing every single line of boilerplate code by hand, we will leverage Codex, our AI assistant. To do this effectively, we cannot simply ask Codex to "build the app." We need to provide it with the right information so it understands our specific project structure.

Concept: Understanding Model Context Protocols (MCP)

AI assistants like Codex are incredibly powerful, but out of the box, they are "blind" to your specific project files. They know how to write Python, but they don't know that you decided to name your user model UserOut or that you are using a specific file structure.

This is where the Model Context Protocol (MCP) comes in.

Think of MCP as a standardized way to "brief" the AI before it starts working. If you hired a contractor to build a house, you wouldn't just say "build a house." You would give them blueprints, local building codes, and material lists. MCP is the digital equivalent of handing over those blueprints. It allows the AI to read your local files, understand your database schema, and see your project structure so that the code it generates fits perfectly into your existing application.

Action: Configuring the Context

For our movie backend, the most critical piece of context is our data structure. In the first lesson, we defined our Pydantic models in movie/backend/api/schemas.py. These models act as the contract for our API.

If we don't show this file to Codex, it might invent its own models, leading to errors where the AI tries to access user.username when we actually defined user.display_name.

To configure the context effectively, we need to ensure Codex is aware of schemas.py. When we prompt Codex, we explicitly reference this file.

For example, a prompt might look like this:

"Using the data models defined in movie/backend/api/schemas.py, implement the FastAPI application in main.py."

By doing this, we ensure that every endpoint Codex generates adheres strictly to the rules we established in our design phase.

Action: Implementing Standard Endpoints

Now, let's look at how we build the application step by step using Codex. We will start with the basic setup and authentication.

Setting up the Store and App

First, we need a place to store our data. Since we are focusing on the API logic, we will use a simple in-memory store (Python dictionaries) instead of a complex database for now.

from fastapi import FastAPI
from uuid import uuid4

# We define a simple class to hold our data in memory
class InMemoryStore:
    def __init__(self):
        self.users = {}
        self.emails = set()
        self.shows = {}
        self.reviews = {}
        self.views = {}

# We initialize the FastAPI app and attach the store to it
def create_app(testing: bool = False) -> FastAPI:
    app = FastAPI(title="Netflix Recommendations Backend")
    app.state.store = InMemoryStore()
    app.state.secret_key = "test-secret" if testing else f"secret-{uuid4().hex}"
    return app

Here, InMemoryStore acts as our database. We attach it to app.state so it is accessible across all our API endpoints.

Implementing Registration

Next, we implement the registration endpoint. This is where the context from schemas.py becomes vital. We need to accept a RegisterRequest and return an AuthResponse.

from fastapi import Depends, HTTPException, status
from .schemas import RegisterRequest, AuthResponse, UserOut

# ... inside create_app ...

    @app.post("/auth/register", response_model=AuthResponse, status_code=status.HTTP_201_CREATED)
    async def register(body: RegisterRequest, store: Annotated[InMemoryStore, Depends(get_store)]):
        # Check if email exists
        if body.email in store.emails:
            raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail="Email already registered")
        
        # Create user object
        user_id = uuid4().hex
        user = {
            "user_id": user_id,
            "email": body.email,
            "display_name": body.display_name,
            # In a real app, we would hash the password here
            "password_hash": "hashed_secret", 
        }
        
        # Save to store
        store.users[user_id] = user
        store.emails.add(body.email)
        
        # Return response matching AuthResponse schema
        return {"user": UserOut(**user), "access_token": "fake-token", "token_type": "bearer"}

Notice how the code uses RegisterRequest for the body argument. Because we gave Codex the context of our schemas, it knows exactly which fields (email, password, display_name) are available on body.

Deep Dive: The Recommendation Logic

The core of our application is the recommendation engine. This is more complex than simple CRUD (Create, Read, Update, Delete) operations. We need to guide Codex to implement specific business logic.

Defining the Scoring Logic

We want to recommend shows based on a "score." A simple logic might be:

  1. Start with the show's average rating.
  2. Add points if the show is popular.
  3. Add points if the show features actors the user likes.

We can implement a helper function for this scoring.

def recommendation_score(store: InMemoryStore, show: dict, preferred_people: set[str]) -> float:
    # 1. Calculate average rating
    avg_rating, rating_count = average_rating(store, show["show_id"])
    
    # 2. Check for actor affinity
    actors = show.get("actors") or []
    directors = show.get("directors") or []
    people = {p.lower() for p in actors + directors}
    
    # Calculate bonus based on overlap with user's preferred people
    affinity_bonus = float(len(preferred_people.intersection(people)))
    
    # 3. Final Score Formula
    return avg_rating + (0.1 * rating_count) + affinity_bonus

This function encapsulates our business logic. It takes a show and a set of people (actors/directors) the user likes and calculates a numerical score.

The Recommendation Endpoint

Finally, we use this score to sort and return the shows.

    @app.get("/users/me/recommendations", response_model=RecommendationsResponse)
    async def recommendations(
        current_user: Annotated[dict, Depends(get_current_user)],
        store: Annotated[InMemoryStore, Depends(get_store)],
        limit: int = 10,
    ):
        # Get list of all shows
        recs = list(store.shows.values())
        
        # Determine who the user likes based on past reviews
        preferred_people = preferred_people_for_user(store, current_user["user_id"])
        
        # Sort shows by our custom score (descending)
        recs = sorted(
            recs,
            key=lambda s: recommendation_score(store, s, preferred_people),
            reverse=True
        )
        
        # Return the top N items
        items = [show_to_out(s) for s in recs[:limit]]
        return {"items": items, "meta": {"limit": limit}}

By breaking the problem down — first defining the score, then sorting by it — we create a recommendation system that is easy to understand and test.

Summary and Practice Prep

In this lesson, we moved from design to implementation.

  1. Context is Key: We learned that MCP allows us to share our project's "blueprints" (like schemas.py) with Codex.
  2. Automated Implementation: We used that context to generate standard endpoints for authentication and data management without writing boilerplate code manually.
  3. Business Logic: We saw how to implement custom logic for recommendations by defining scoring functions.

Now it is your turn. In the upcoming practice, you will be tasked with implementing the full main.py file. You will need to ensure Codex has the right context to generate the endpoints that satisfy the tests we wrote in the previous lesson. Good luck!

Sign up

Join the 1M+ learners on CodeSignal

Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal