Build an Internship Tracker API

Build a tested FastAPI and SQLite API to track internship applications.

Introduction

30 Second Summary

Internship applications quickly scatter across all the places you search for work. A missed status update can make an interview or follow-up disappear from view.

In this project, you will build a local internship application tracker as a REST API with FastAPI. It will validate each record before preserving it in SQLite.

What You'll Build

With the finished API running, interactive browser documentation gives you one place to manage internship applications that remain saved after server restarts.

By the end of this project, you'll have:

  • A browser-based CRUD workflow covering the full lifecycle of every internship application.
  • A validated SQLite tracker that keeps every application after server restarts.
  • A portfolio-ready GitHub repository with passing automated tests plus clear setup instructions.
  • Secret Mission: A validated status filter that shows only applications at a selected stage.

What do I need before starting?

You need Visual Studio Code on Windows plus basic comfort with one programming language. The first step guides you through every remaining tool and account.

Before We Start

A clear problem statement gives every technical choice in this project a purpose. This is your moment to define who needs an internship application tracker and why losing track of applications is frustrating.

Set Up Your Windows Backend Workspace

Your one-sentence problem statement gives the tracker a clear reason to exist. Now you need a workspace that can support the build.

A reproducible workspace prevents version drift. It also prevents missing dependencies from distracting you once the API work begins.

In this step, get ready to:
  • Prepare Python and Git for local development.
  • Create a verified GitHub Free account.
  • Build an isolated Visual Studio Code workspace with pinned dependencies.
Install Python and Git

Python runs the API code. Git records the project history that you publish later.

  • Press the Windows key to open the Windows search bar.
  • Type Command Prompt and press Enter.

You should see a Command Prompt window with a blinking cursor. This terminal lets you check both installations before creating the project.

  • Check which Python version is available by running this command:
python --version

What does this check?

This command prints the Python version attached to the current Command Prompt session. The project uses 3.14.9 so every learner runs the same runtime.

✔️ I see the required Python version

Your output shows Python 3.14.9. Python is ready for the project.

ⓧ I see another Python version

Your current Python version does not match the project runtime. Installing 3.14.9 gives the project a consistent starting point.

The installer presents several setup choices. Keep the default components during the installation.

  • Visit the official Python Windows downloads page.
  • Select Download Windows installer (64-bit) for Python 3.14.9.
  • Run the downloaded Windows installer.
  • Choose the installer option that adds Python to your command path.
  • Complete the installation with the default components.

The required Python runtime is now installed. A new Command Prompt session can detect the updated command path.

  • Close the current Command Prompt window.
  • Press the Windows key to reopen the Windows search bar.
  • Type Command Prompt and press Enter.
  • Verify the new Python installation by running this command:
python --version

What should you see?

The refreshed terminal should report Python 3.14.9. This confirms that the command path points to the project runtime.

ⓧ Command not found

Windows cannot find a Python installation from this terminal. The official 64-bit installer adds the runtime you need.

The installer presents several setup choices. Keep the default components during the installation.

  • Visit the official Python Windows downloads page.
  • Select Download Windows installer (64-bit) for Python 3.14.9.
  • Run the downloaded Windows installer.
  • Choose the installer option that adds Python to your command path.
  • Complete the installation with the default components.

Python is now installed. A fresh Command Prompt session can detect the new command.

  • Close the current Command Prompt window.
  • Press the Windows key to reopen the Windows search bar.
  • Type Command Prompt and press Enter.
  • Verify the Python installation by running this command:
python --version

What should you see?

The new terminal should report Python 3.14.9. This confirms that Windows can run Python from the command line.

  • Check which Git version is available by running this command:
git --version

What does this check?

This command asks the installed Git suite to report its version. The project uses Git for Windows 2.56.0(2).

✔️ I see the required Git version

Your output identifies the required Git for Windows release. Git is ready to track the project.

ⓧ I see an older Git version

Your current Git version needs an update. The maintained x64 setup provides Git for Windows 2.56.0(2).

Windows may ask for permission to run the setup program. Approve that request because the installer comes from the official Git website.

  • Visit the official Git for Windows installation page.
  • Download the maintained x64 setup for Git for Windows 2.56.0(2).
  • Run the downloaded setup program.
  • Keep the default options throughout the setup.
  • Close the current Command Prompt window after setup finishes.
  • Open a fresh Command Prompt through the Windows search bar.
  • Verify the updated Git installation by running this command:
git --version

What should you see?

The new terminal should identify an installed Git version. The maintained setup corresponds to Git for Windows 2.56.0(2).

ⓧ Command not found

Windows cannot find Git from this terminal. The official x64 setup installs the command-line tools required for version control.

Windows may ask for permission to run the setup program. Approve that request because the installer comes from the official Git website.

  • Visit the official Git for Windows installation page.
  • Download the maintained x64 setup for Git for Windows 2.56.0(2).
  • Run the downloaded setup program.
  • Keep the default options throughout the setup.
  • Close the current Command Prompt window after setup finishes.
  • Open a fresh Command Prompt through the Windows search bar.
  • Verify the Git installation by running this command:
git --version

What should you see?

The new terminal should identify an installed Git version. The maintained setup corresponds to Git for Windows 2.56.0(2).

Git attaches a name and email address to each commit. Configure these values now so your future project history identifies you.

  • Replace Your Name in the first command with your real name.
  • Replace you@example.com in the second command with your real email address.
  • Save your global Git identity by running the edited commands:
git config --global user.name "Your Name"
git config --global user.email "you@example.com"

What do these commands save?

The --global option stores user.name and user.email for repositories on this Windows account. Future commits can use this identity automatically.

The terminal returns to the prompt without an error. Your global commit identity is now configured.

Create and verify your GitHub account

GitHub stores the project repository that you publish in the final step. A verified email address lets the account create that repository.

Your password stays inside GitHub's signup form. It never belongs in the project files.

  • Visit the GitHub signup page in your browser.
  • Enter your personal email address in the signup form.
  • Choose a GitHub username for your account.
  • Create a secure password inside the signup form.
  • Complete the remaining account creation prompts.
  • Open the verification message sent to your email inbox.
  • Follow the verification link in that message.

GitHub confirms that your email address is verified. Your account is ready to receive the project repository.

  • Leave the verified GitHub account signed in within your browser.

Verification message not arriving?

  • Check the spam or junk folder for the GitHub verification message.
  • Confirm that the signup email address contains no typing mistakes.
  • Request another verification message from your GitHub account when the first message does not arrive.

Need help with account verification?

Build the project workspace

A virtual environment gives this project its own package installation. The pinned dependency file makes that environment reproducible.

  • Press the Windows key to open the Windows search bar.
  • Type Visual Studio Code and press Enter.

You should see the Visual Studio Code window. The editor is ready to open a local project folder.

  • Click File in the top menu bar.
  • Click Open Folder.

You should see the Windows folder picker. This is where you choose the exact location for the project.

  • Select Desktop in the folder picker.
  • Click the control for creating a new folder.

A new folder name field should appear on your Desktop. This folder keeps every local project artifact together.

  • Enter internship-tracker-api as the folder name.
  • Select the new internship-tracker-api folder.
  • Click Select Folder.

Visual Studio Code may ask whether you trust this folder. The folder is safe to trust because you created it yourself.

  • Confirm that you trust the folder if Visual Studio Code asks.

You should now see internship-tracker-api at the top of the Explorer sidebar. The empty local workspace is open.

The integrated terminal runs commands from the open folder. Using Command Prompt keeps the activation command consistent with this Windows guide.

  • Click Terminal in the top menu bar.
  • Click New Terminal.

You should see a terminal panel at the bottom of Visual Studio Code. Its prompt should point to the open project folder.

  • Open the terminal profile selector beside the new-terminal control.
  • Select Command Prompt as the terminal profile.

The new terminal should identify itself as Command Prompt. Its path should end with internship-tracker-api.

  • Create the isolated .venv environment by running this command:
python -m venv .venv

What does this command create?

Python creates a .venv folder inside internship-tracker-api. Packages installed after activation stay isolated from other Python projects.

  • Confirm that .venv appears in the Visual Studio Code Explorer sidebar.

The .venv folder proves that Python created the isolated environment on disk.

  • Activate the virtual environment in Command Prompt by running this command:
.venv\Scripts\activate.bat

What does activation change?

Activation points the current terminal at the Python executable inside .venv. Package installation now targets this project environment.

You should see (.venv) at the start of the terminal prompt. That visible prefix confirms that the environment is active.

The dependency file records the exact package versions used by the project. Keeping those pins in one file makes the setup repeatable.

  • Select the internship-tracker-api folder in the Explorer sidebar.
  • Click the new-file control at the top of the Explorer sidebar.

An empty file name field should appear under the project folder. The new file belongs directly inside internship-tracker-api.

  • Enter requirements.txt as the file name.
  • Add the pinned dependencies to requirements.txt by pasting this exact content:
fastapi==0.159.1
pydantic==2.14.1
uvicorn[standard]==0.54.0
httpx==0.28.1
pytest==9.1.1

What do these dependencies provide?

  • FastAPI 0.159.1 provides the API framework.
  • Pydantic 2.14.1 validates incoming application data.
  • Uvicorn 0.54.0 runs the local development server.
  • HTTPX 0.28.1 supports requests made by the test client.
  • pytest 9.1.1 discovers and runs the automated tests.
  • Save requirements.txt from the Visual Studio Code File menu.

✔️ Awesome, I've got everything!

Great. Your saved requirements.txt file contains all five pinned dependencies.

ⓧ I'd like to double check the full code

fastapi==0.159.1
pydantic==2.14.1
uvicorn[standard]==0.54.0
httpx==0.28.1
pytest==9.1.1

How should this file match?

Compare each package name with your saved file. Compare each pinned version before continuing.

  • Install every package listed in requirements.txt by running this command:
python -m pip install -r requirements.txt

What does this command install?

Python asks pip to read the package pins from requirements.txt. Pip installs those dependencies inside the active .venv environment.

The command should finish and return to the (.venv) prompt without an installation error. The project dependencies are now available to Python.

Dependency installation failing?

  • Confirm that (.venv) appears at the start of the terminal prompt.
  • Check that the dependency file is named exactly requirements.txt.
  • Confirm that your internet connection can reach the Python package registry.

Help me diagnose the dependency installation.

Before you run the final check, which three success signals do you expect the terminal to show?

  • Verify the complete backend workspace by running these commands:
python --version
git --version
python -c "from fastapi import FastAPI; print('FastAPI ready')"

What does the final check prove?

  • The first command confirms which Python runtime the activated environment uses.
  • The second command confirms that Git is available from the Visual Studio Code terminal.
  • The third command imports FastAPI and prints a confirmation when the dependency is ready.

Together, these checks prove that the runtime, version-control tool, and API framework are accessible from one workspace.

The first line shows Python 3.14.9. The second line identifies an installed Git version.

The final line shows FastAPI ready. That is the full Windows backend workspace working from inside the activated environment.

Your backend workspace is ready. Next, you will turn it into a running API with a browser-visible health check.

Launch a Visible Health Check

Your Windows backend workspace is ready. Now you can use FastAPI to turn that setup into a request your browser can reach.

Application logic can hide basic connection problems inside larger failures. A tiny health endpoint proves the request and response loop before you add internship records.

In this step, get ready to:
  • Create the FastAPI application with a health endpoint.
  • Start Uvicorn with automatic reloads.
  • Verify the JSON response through your browser and the interactive documentation.
Create the health endpoint

A health endpoint gives you a small request with a predictable response. It creates a clear baseline for every feature that follows.

  • Create main.py beside requirements.txt using the new-file control in the Explorer sidebar of VS Code.

You should see main.py appear inside the internship-tracker-api folder.

  • Paste the following code into main.py:
from fastapi import FastAPI

app = FastAPI(title="Internship Application Tracker API")


@app.get("/health")
def health():
    return {"status": "ok"}

What does this code do?

  • The app = FastAPI(title="Internship Application Tracker API") line creates the application that Uvicorn serves.
  • The @app.get("/health") decorator connects a GET request to the health function.
  • The health() function returns a small response that FastAPI converts to JSON.
  • Save main.py.

✔️ Awesome, I've got everything!

Your saved main.py now contains the application and its health endpoint.

ⓧ I'd like to double check the full code

The complete file should match this reference.

from fastapi import FastAPI

app = FastAPI(title="Internship Application Tracker API")


@app.get("/health")
def health():
    return {"status": "ok"}
Run the local API

Uvicorn connects the app object in main.py to your browser. The --reload option watches your project for saved changes.

Before you run the server, do you expect its terminal to accept more commands or stay busy serving requests?

  • Start the development server in the activated Command Prompt terminal by running this command:
uvicorn main:app --reload

What does this command do?

The main:app value points Uvicorn to the app object inside main.py. The --reload option restarts the development server after saved code changes.

The terminal stays busy while Uvicorn serves your application. Keep this process running for the browser checks.

Before you check the endpoint, what response do you expect the code in health() to produce?

  • Open a new browser tab.
  • Enter http://127.0.0.1:8000/health in the address bar.

You should see {"status":"ok"} in the browser. You have your first live API response.

Health page not loading?

  • Confirm the Uvicorn process is still running in the VS Code terminal.
  • Confirm the terminal still uses the activated .venv environment.
  • Check that the browser address matches http://127.0.0.1:8000/health exactly.

Help me debug why my FastAPI health endpoint is not loading.

FastAPI also generates Swagger UI from your path operations. It gives you a browser interface for running the same request without writing a separate client.

Before you run the health operation in Swagger UI, do you expect its response to match the direct browser response?

  • Open another browser tab.
  • Enter http://127.0.0.1:8000/docs in the address bar.
  • Select the GET /health operation.
  • Run the health operation from its panel.

You should see the health operation return {"status":"ok"}. You now have two visible ways to prove that the API is alive.

Your request and response loop is live. Next, you will add the first internship application records to this API.

Build CRUD in Memory

Your health endpoint proved that requests can reach your FastAPI server. Now the tracker needs to handle a real application record as JSON.

This first CRUD slice creates a record. A second operation reads it back. An in-memory list keeps that loop small. You will restart the process to test how dependable that storage is.

In this step, get ready to:
  • Define validated application records with Pydantic.
  • Connect those records to an in-memory API.
  • Restart Uvicorn to test whether one record survives.
Define the application model

Each application needs a consistent shape before the API can trust it. A request model checks incoming fields against your constraints.

  • In main.py, find the existing from fastapi import FastAPI line.
  • Replace that line by copying the code below:
from typing import Literal

from fastapi import FastAPI
from pydantic import BaseModel, Field

What do these imports add?

  • Literal gives a field a fixed set of accepted values.
  • BaseModel supplies the foundation for the validated request body.
  • Field applies length limits to individual values.
  • In main.py, find the existing app = FastAPI(title="Internship Application Tracker API") line.
  • Replace that line by copying the model and temporary store below:
ApplicationStatus = Literal["saved", "applied", "interview", "offer", "rejected"]


class ApplicationInput(BaseModel):
    company: str = Field(min_length=1, max_length=100)
    role: str = Field(min_length=1, max_length=100)
    status: ApplicationStatus = "saved"
    notes: str = Field(default="", max_length=500)


app = FastAPI(title="Internship Application Tracker API")
applications = []

How does validation work?

  • ApplicationStatus permits five application stages. Unsupported values fail validation before the endpoint runs.
  • ApplicationInput defines the four values accepted from each request.
  • company and role must contain between 1 and 100 characters.
  • notes accepts up to 500 characters. Its default value is an empty string.
  • applications holds the records created during the current server process.
  • Save main.py.
  • Switch back to the VS Code Command Prompt terminal from earlier.
  • Confirm the automatic reload completes without a traceback.

Seeing an import or syntax error?

Compare the three import lines with the first code block. Make sure every field inside ApplicationInput has four spaces of indentation.

Check that applications = [] sits outside the class.

Help me diagnose the model error in my FastAPI file.

Add create and list operations

FastAPI path operations connect an HTTP method to a function. The create operation receives the validated request model.

  • In main.py, find the health() function.
  • Add the create operation below its return {"status": "ok"} line by copying this code:
@app.post("/applications", status_code=201)
def create_application(application: ApplicationInput):
    record = {"id": len(applications) + 1, **application.model_dump()}
    applications.append(record)
    return record

What does the create operation do?

  • @app.post("/applications", status_code=201) exposes an operation for creating applications. Successful requests receive status 201.
  • application.model_dump() converts the validated model into a dictionary.
  • len(applications) + 1 generates the next record ID from the current list length.
  • applications.append(record) keeps the new record in the running process.
  • Save main.py.
  • Return to Swagger UI at http://127.0.0.1:8000/docs.
  • Refresh the browser page.

You will see the new POST /applications operation. Its request body contains the four fields from ApplicationInput.

Create operation missing?

Check the Uvicorn terminal for a traceback. Confirm that the @app.post decorator starts at the left edge of main.py.

Help me find why POST /applications is missing from Swagger UI.

  • In main.py, find the return record line inside create_application().
  • Add the list operation below that function by copying this code:
@app.get("/applications")
def list_applications():
    return applications

What does the list operation do?

  • @app.get("/applications") exposes the collection through a read operation.
  • list_applications() returns every record currently held in applications.
  • Save main.py.
  • Return to Swagger UI from earlier.
  • Refresh the browser page.
  • Expand the POST /applications operation.

The request editor shows the four fields defined by your application model.

  • Set the company value to Example Labs.
  • Set the role value to Backend Intern.
  • Set the status value to applied.
  • Set the notes value to Submitted through campus portal.
  • Submit the create request from the operation panel.

You will receive status 201. The response body includes a generated id of 1.

  • Expand the GET /applications operation.
  • Submit the list request from the operation panel.

You will see a JSON list containing the Example Labs record. That is your first tracker flow working.

Record missing from the list?

Confirm that the create response showed status 201 before you submitted the list request. Check that applications.append(record) remains inside create_application().

Help me find why GET /applications does not return my created record.

✔️ Awesome, I've got everything!

Your main.py file now contains the validated model plus both in-memory operations.

ⓧ I'd like to double check the full code

from typing import Literal

from fastapi import FastAPI
from pydantic import BaseModel, Field

ApplicationStatus = Literal["saved", "applied", "interview", "offer", "rejected"]


class ApplicationInput(BaseModel):
    company: str = Field(min_length=1, max_length=100)
    role: str = Field(min_length=1, max_length=100)
    status: ApplicationStatus = "saved"
    notes: str = Field(default="", max_length=500)


app = FastAPI(title="Internship Application Tracker API")
applications = []


@app.get("/health")
def health():
    return {"status": "ok"}


@app.post("/applications", status_code=201)
def create_application(application: ApplicationInput):
    record = {"id": len(applications) + 1, **application.model_dump()}
    applications.append(record)
    return record


@app.get("/applications")
def list_applications():
    return applications
Restart the server and test memory

The Example Labs record currently lives in the applications list owned by the running server process. A restart gives you a direct persistence test.

Before you restart the server, do you think GET /applications will still return the Example Labs record?

  • Stop the running Uvicorn server by pressing CTRL + C in the VS Code terminal.
  • Restart the development server by running this command:
uvicorn main:app --reload

What does this command do?

  • main:app points Uvicorn to the app object inside main.py.
  • --reload watches the project files for saved changes during development.
  • Wait until the terminal confirms the server has started.
  • Return to http://127.0.0.1:8000/docs.
  • Refresh the browser page.
  • Expand the GET /applications operation.
  • Submit the list request from the operation panel.

You will see [], even though the earlier create request returned a record. This shortfall is intentional.

The list is volatile storage because its contents belong to one running process. Your tracker now proves why application records need durable storage.

You have built validated create and list operations. Next, you will move the records into on-disk storage so they can survive a restart.

Persist Applications with SQLite

Your in-memory FastAPI API proved that the application flow works. Restarting Uvicorn erased every record because the list only lived inside the running process.

SQLite stores each record in an on-disk database file. You will replace the temporary list with persistent storage that survives server restarts.

In this step, get ready to:
  • Create the database connection helpers and the applications table.
  • Move the create and list operations to parameterized SQL queries.
  • Add update and delete operations with explicit HTTP error responses.
Build the SQLite foundation

The database layer needs one file location plus a consistent way to open connections. The table definition also protects the allowed status values at the storage layer.

  • In main.py select the import lines at the top of the file.
  • Replace the selected lines by pasting the following code:
import sqlite3
from pathlib import import Path
from typing import Literal

from fastapi import FastAPI, HTTPException
from pydantic import BaseModel, Field

DATABASE = Path("applications.db")

What do these imports prepare?

  • The sqlite3 module provides the database connection used throughout the API.
  • Path represents the location of applications.db.
  • HTTPException lets the API return a clear missing-record response.
  • DATABASE gives every operation one shared database location.
  • Save main.py.
  • Refresh http://127.0.0.1:8000/health in your browser.

You should still see {"status":"ok"}. This confirms that the new imports load successfully.

Does the health request fail?

Check that Path comes from pathlib. Confirm that HTTPException appears in the existing FastAPI import.

Need help finding the import problem?

A connection helper keeps the same row behavior across every path operation. The initialization function creates the table once while preserving it on later reloads.

  • Find the line that defines app = FastAPI(title="Internship Application Tracker API").
  • Insert the following code directly below that line and above applications = []:
def get_connection():
    connection = sqlite3.connect(DATABASE)
    connection.row_factory = sqlite3.Row
    return connection


def init_db():
    connection = get_connection()
    try:
        connection.execute(
            """
            CREATE TABLE IF NOT EXISTS applications (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                company TEXT NOT NULL,
                role TEXT NOT NULL,
                status TEXT NOT NULL
                    CHECK (status IN ('saved', 'applied', 'interview', 'offer', 'rejected')),
                notes TEXT NOT NULL DEFAULT ''
            )
            """
        )
        connection.commit()
    finally:
        connection.close()

How does the table work?

  • get_connection() opens applications.db and configures rows with named columns.
  • AUTOINCREMENT gives each application a generated ID.
  • CHECK limits stored statuses to the five values accepted by ApplicationInput.
  • finally closes the connection even when database work fails.
  • Save main.py.
  • Refresh the health endpoint once Uvicorn reloads.

You should see the same healthy JSON response. The table helper is now valid Python code.

Does Uvicorn stop reloading?

Check that the triple quotes surround the complete CREATE TABLE statement. Match the closing parentheses with the code above.

Help me fix the table definition.

Database rows need to become JSON-friendly dictionaries before FastAPI returns them. A shared lookup helper also gives every ID-based operation the same missing-record behavior.

  • Find the end of init_db() in main.py.
  • Insert the following code below the function and above applications = []:
def row_to_dict(row: sqlite3.Row) -> dict:
    return {key: row[key] for key in row.keys()}


def fetch_application(connection, application_id: int) -> dict:
    row = connection.execute(
        "SELECT id, company, role, status, notes FROM applications WHERE id = ?",
        (application_id,),
    ).fetchone()
    if row is None:
        raise HTTPException(status_code=404, detail="Application not found")
    return row_to_dict(row)


init_db()

What does this code add?

  • row_to_dict() maps each column name to its stored value.
  • fetch_application() retrieves one record with a parameterized ID.
  • HTTPException returns a 404 response when the ID does not exist.
  • init_db() creates the database table as soon as the application starts.
  • Save main.py.
  • Confirm that Uvicorn completes its automatic reload.
  • Confirm that applications.db appears in the VS Code Explorer sidebar.

That database file is your first persistent artifact. Future server processes can reopen the same file.

Database file missing?

Confirm that init_db() sits below fetch_application(). Check the Uvicorn terminal for a Python syntax problem before looking for the file.

Help me create applications.db.

Move create and list into SQLite

The database exists, but the current path operations still use the temporary list. The create operation now needs to insert validated values and return the generated database row.

  • Find the existing create_application() function in main.py.
  • Replace its decorator and complete function with the following code:
@app.post("/applications", status_code=201)
def create_application(application: ApplicationInput):
    connection = get_connection()
    try:
        cursor = connection.execute(
            """
            INSERT INTO applications (company, role, status, notes)
            VALUES (?, ?, ?, ?)
            """,
            (
                application.company,
                application.role,
                application.status,
                application.notes,
            ),
        )
        connection.commit()
        return fetch_application(connection, cursor.lastrowid)
    finally:
        connection.close()

How does creation become persistent?

  • The four question marks keep submitted values separate from the SQL statement.
  • connection.commit() saves the inserted row to applications.db.
  • cursor.lastrowid captures the ID generated by SQLite.
  • fetch_application() returns the completed record with its ID.
  • Save main.py.
  • Return to the Swagger UI at http://127.0.0.1:8000/docs.
  • Select the POST /applications operation.
  • Start an editable request in the operation.
  • Replace the request body with the following JSON:
{
                    "company": "Example Labs",
                    "role": "Backend Intern",
                    "status": "applied",
                    "notes": "Submitted through campus portal"
                }

What does this request represent?

This request contains the four fields validated by ApplicationInput. SQLite generates the ID after the request passes validation.

  • Submit the request from the operation.

You should receive a 201 response. The response body should include the Example Labs record with a generated id.

Does the create request fail?

Confirm that every question mark has a matching value in the tuple. Check that connection.commit() appears before the return statement.

Help me debug the SQLite insert.

The list operation now needs to read database rows in a predictable order. Converting each row produces the JSON array expected by API clients.

  • Find the existing list_applications() function.
  • Replace its decorator and complete function with the following code:
@app.get("/applications")
def list_applications():
    connection = get_connection()
    try:
        rows = connection.execute(
            "SELECT id, company, role, status, notes FROM applications ORDER BY id DESC"
        ).fetchall()
        return [row_to_dict(row) for row in rows]
    finally:
        connection.close()

How does listing work?

  • The query selects every public application field from the table.
  • ORDER BY id DESC places the newest generated ID first.
  • fetchall() collects all matching rows.
  • The list comprehension converts every SQLite row into a dictionary.
  • Save main.py.
  • Select the GET /applications operation in Swagger UI.
  • Submit the list request.

You should see the Example Labs record inside a JSON array. The response now comes from applications.db.

  • Delete the unused applications = [] line from main.py.
  • Save main.py.
  • Submit the GET /applications request again.

The record should remain in the response. Your API now reads and writes through SQLite without the temporary list.

Is the list empty?

Submit the create request once more if no row was committed. Confirm that list_applications() calls fetchall() before closing the connection.

Help me trace the missing record.

Add update and delete operations

A complete tracker must change an existing application after its status advances. The update operation first verifies the ID before replacing the stored fields.

  • Find the end of list_applications() in main.py.
  • Insert the following update operation directly below it:
@app.put("/applications/{application_id}")
def update_application(application_id: int, application: ApplicationInput):
    connection = get_connection()
    try:
        fetch_application(connection, application_id)
        connection.execute(
            """
            UPDATE applications
            SET company = ?, role = ?, status = ?, notes = ?
            WHERE id = ?
            """,
            (
                application.company,
                application.role,
                application.status,
                application.notes,
                application_id,
            ),
        )
        connection.commit()
        return fetch_application(connection, application_id)
    finally:
        connection.close()

How does the update stay safe?

  • The first fetch_application() call stops the operation when the ID is missing.
  • The parameterized query separates all submitted values from the SQL text.
  • The final lookup returns the saved version of the updated record.
  • Save main.py.
  • Select the PUT /applications/{application_id} operation in Swagger UI.
  • Enter the generated ID from your Example Labs record.
  • Reuse the request body from the create operation.
  • Change the status value from applied to interview.
  • Submit the update request.

You should receive the same generated ID with status set to interview. The changed value is now stored in SQLite.

Does the update return a missing record?

Use an ID shown by the latest list request. Confirm that the final tuple includes application_id after the four model values.

Help me fix the update operation.

Deletion uses the same lookup rule as updating. A second request for a deleted ID proves that missing records produce a clear API response.

  • Find the end of update_application().
  • Insert the following delete operation directly below it:
@app.delete("/applications/{application_id}")
def delete_application(application_id: int):
    connection = get_connection()
    try:
        fetch_application(connection, application_id)
        connection.execute(
            "DELETE FROM applications WHERE id = ?",
            (application_id,),
        )
        connection.commit()
        return {"message": "Application deleted"}
    finally:
        connection.close()

How does deletion work?

  • fetch_application() confirms that the requested ID exists.
  • The parameter tuple supplies the ID without placing it inside the SQL text.
  • The committed deletion returns a concise confirmation message.
  • Save main.py.
  • Select the DELETE /applications/{application_id} operation in Swagger UI.
  • Enter the ID of the Example Labs record.
  • Submit the delete request.

You should see {"message":"Application deleted"} in the response.

  • Submit the delete request again with the same ID.

You should receive a 404 response with Application not found. This confirms that missing IDs follow the shared error path.

Does deletion leave the record behind?

Confirm that connection.commit() runs after the delete query. Use the same ID in a list request to check whether the row remains.

Help me debug the delete operation.

The final check proves that the database survives the exact restart that erased your in-memory record. You will create one final record before stopping the server.

  • Select the POST /applications operation in Swagger UI.
  • Reuse the Example Labs request body.
  • Submit the create request.
  • Select the GET /applications operation.
  • Submit the list request.

You should see the final Example Labs record with its generated ID. This is the record that tests persistence.

  • Return to the VS Code terminal running Uvicorn.
  • Stop the server by pressing Ctrl+C.

Before you restart the server, do you expect the final record to remain or disappear?

  • Restart Uvicorn with auto-reload by running this command:
uvicorn main:app --reload

What does this restart prove?

The command starts a new application process from main.py. That new process opens the existing applications.db file.

  • Return to Swagger UI after the server starts.
  • Select the GET /applications operation.
  • Submit the list request.

You should see the same final record with the same generated ID. That is the key difference from the in-memory version.

Did the record disappear after restart?

Confirm that VS Code and Uvicorn use the same internship-tracker-api folder. Check that DATABASE still points to applications.db.

Help me trace the database file.

Compare your completed file before moving on.

✔️ Awesome, I've got everything!

Your main.py now provides persistent create, list, update, and delete operations.

ⓧ I'd like to double check the full code

import sqlite3
from pathlib import Path
from typing import Literal

from fastapi import FastAPI, HTTPException
from pydantic import BaseModel, Field

DATABASE = Path("applications.db")
ApplicationStatus = Literal["saved", "applied", "interview", "offer", "rejected"]


class ApplicationInput(BaseModel):
    company: str = Field(min_length=1, max_length=100)
    role: str = Field(min_length=1, max_length=100)
    status: ApplicationStatus = "saved"
    notes: str = Field(default="", max_length=500)


app = FastAPI(title="Internship Application Tracker API")


def get_connection():
    connection = sqlite3.connect(DATABASE)
    connection.row_factory = sqlite3.Row
    return connection


def init_db():
    connection = get_connection()
    try:
        connection.execute(
            """
            CREATE TABLE IF NOT EXISTS applications (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                company TEXT NOT NULL,
                role TEXT NOT NULL,
                status TEXT NOT NULL
                    CHECK (status IN ('saved', 'applied', 'interview', 'offer', 'rejected')),
                notes TEXT NOT NULL DEFAULT ''
            )
            """
        )
        connection.commit()
    finally:
        connection.close()


def row_to_dict(row: sqlite3.Row) -> dict:
    return {key: row[key] for key in row.keys()}


def fetch_application(connection, application_id: int) -> dict:
    row = connection.execute(
        "SELECT id, company, role, status, notes FROM applications WHERE id = ?",
        (application_id,),
    ).fetchone()
    if row is None:
        raise HTTPException(status_code=404, detail="Application not found")
    return row_to_dict(row)


init_db()


@app.get("/health")
def health():
    return {"status": "ok"}


@app.post("/applications", status_code=201)
def create_application(application: ApplicationInput):
    connection = get_connection()
    try:
        cursor = connection.execute(
            """
            INSERT INTO applications (company, role, status, notes)
            VALUES (?, ?, ?, ?)
            """,
            (
                application.company,
                application.role,
                application.status,
                application.notes,
            ),
        )
        connection.commit()
        return fetch_application(connection, cursor.lastrowid)
    finally:
        connection.close()


@app.get("/applications")
def list_applications():
    connection = get_connection()
    try:
        rows = connection.execute(
            "SELECT id, company, role, status, notes FROM applications ORDER BY id DESC"
        ).fetchall()
        return [row_to_dict(row) for row in rows]
    finally:
        connection.close()


@app.put("/applications/{application_id}")
def update_application(application_id: int, application: ApplicationInput):
    connection = get_connection()
    try:
        fetch_application(connection, application_id)
        connection.execute(
            """
            UPDATE applications
            SET company = ?, role = ?, status = ?, notes = ?
            WHERE id = ?
            """,
            (
                application.company,
                application.role,
                application.status,
                application.notes,
                application_id,
            ),
        )
        connection.commit()
        return fetch_application(connection, application_id)
    finally:
        connection.close()


@app.delete("/applications/{application_id}")
def delete_application(application_id: int):
    connection = get_connection()
    try:
        fetch_application(connection, application_id)
        connection.execute(
            "DELETE FROM applications WHERE id = ?",
            (application_id,),
        )
        connection.commit()
        return {"message": "Application deleted"}
    finally:
        connection.close()

This complete file initializes the SQLite table at startup. Every application operation now uses the same database helpers.

Your tracker now keeps validated records across process restarts. Next, you will prove its behavior with automated tests and publish the finished project.

Test and Publish the Project

Your FastAPI API now validates internship applications. Records survive restarts in SQLite.

Automated tests turn that working behavior into repeatable evidence. Documentation plus visible Git history make the project ready for someone else to inspect on GitHub.

In this step, get ready to:
  • Prove the API behavior with two automated tests.
  • Document the project while keeping local data out of version control.
  • Publish the committed project to a public GitHub repository.
Create automated API tests

The test suite checks the smallest working request first. It then checks a complete create-and-list flow without touching the records in applications.db.

Why use a temporary database?

Each test should start with a predictable database. A temporary database gives the CRUD test an isolated file that disappears after the test finishes.

The test restores main.DATABASE before it exits. Your saved internship records stay intact.

  • Select the internship-tracker-api folder in the VS Code Explorer sidebar.
  • Click the new-file control beside the folder name.
  • Name the file test_main.py.
  • Set up the test client by pasting this code into test_main.py:
from pathlib import Path
from tempfile import TemporaryDirectory

from fastapi.testclient import TestClient

import main

client = TestClient(main.app)

What does this setup do?

  • Path builds the location of the temporary database.
  • TemporaryDirectory provides an isolated folder for that database.
  • TestClient sends requests to the application without starting a live server.
  • main gives the tests access to the FastAPI application and its database path.
  • Save test_main.py.
  • Confirm that test_main.py appears beside main.py in the VS Code Explorer sidebar.

Seeing an import problem?

Confirm that test_main.py is in the same folder as main.py.

Check that the terminal prompt still shows the activated .venv environment.

Help me diagnose the test imports in my FastAPI project.

The health test confirms that the API still returns its established success response. This gives the suite a fast check of the request loop.

  • Place the cursor below the client line in test_main.py.
  • Add the health test by pasting this code:
def test_health():
    response = client.get("/health")
    assert response.status_code == 200
    assert response.json() == {"status": "ok"}

What does this test prove?

  • The test client sends a GET request to /health.
  • The first assertion checks the successful HTTP status code.
  • The second assertion checks the exact JSON response.
  • Save test_main.py.
  • Run the health test from the activated terminal with this command:
pytest

What does this command do?

pytest discovers test functions inside files such as test_main.py. It runs functions whose names begin with test_.

You should see one passing test. The result proves that the health request works through the automated test client.

Is the health test failing?

Compare the expected JSON with the health() function in main.py.

Confirm that the test function is named test_health so pytest can discover it.

Help me fix my failing FastAPI health test.

The CRUD test writes data, so it needs stronger isolation. It temporarily points main.DATABASE at a separate test.db file.

  • Place the cursor below test_health() in test_main.py.
  • Add the isolated create-and-list test by pasting this code:
def test_create_and_list_application():
    original_database = main.DATABASE

    with TemporaryDirectory() as temp_directory:
        main.DATABASE = Path(temp_directory) / "test.db"
        main.init_db()

        try:
            create_response = client.post(
                "/applications",
                json={"company": "Example Labs", "role": "Backend Intern", "status": "applied", "notes": "Submitted through campus portal"},
            )
            assert create_response.status_code == 201
            assert create_response.json()["company"] == "Example Labs"

            list_response = client.get("/applications")
            assert list_response.status_code == 200
            assert len(list_response.json()) == 1
            assert list_response.json()[0]["status"] == "applied"
        finally:
            main.DATABASE = original_database

How does database isolation work?

  • The test saves the original main.DATABASE value before changing it.
  • The temporary directory contains a separate test.db database.
  • The POST assertions confirm that the API creates the expected company record.
  • The GET assertions confirm that the isolated database contains one record with the expected status.
  • The finally block restores the development database path even when an assertion fails.
  • Save test_main.py.

Before you run the complete suite, do you expect either test to change the records in applications.db?

  • Run both automated tests with this command:
pytest

What should the suite confirm?

The suite checks the health response. It also checks the isolated SQLite create-and-list flow.

Your development database stays untouched because the second test uses a temporary file.

You should see 2 passed. You now have repeatable proof for the health endpoint and the core persistence flow.

Are fewer than two tests passing?

Confirm that both function names begin with test_.

Check that main.DATABASE is restored inside the finally block.

Help me debug my two pytest tests.

✔️ Awesome, I've got everything!

Your two tests pass. Save test_main.py before continuing.

ⓧ I'd like to double check the full code

from pathlib import Path
from tempfile import TemporaryDirectory

from fastapi.testclient import TestClient

import main

client = TestClient(main.app)


def test_health():
    response = client.get("/health")
    assert response.status_code == 200
    assert response.json() == {"status": "ok"}


def test_create_and_list_application():
    original_database = main.DATABASE

    with TemporaryDirectory() as temp_directory:
        main.DATABASE = Path(temp_directory) / "test.db"
        main.init_db()

        try:
            create_response = client.post(
                "/applications",
                json={"company": "Example Labs", "role": "Backend Intern", "status": "applied", "notes": "Submitted through campus portal"},
            )
            assert create_response.status_code == 201
            assert create_response.json()["company"] == "Example Labs"

            list_response = client.get("/applications")
            assert list_response.status_code == 200
            assert len(list_response.json()) == 1
            assert list_response.json()[0]["status"] == "applied"
        finally:
            main.DATABASE = original_database

How should I use this reference?

Compare this complete reference with your saved test_main.py file. Check the imports and both test functions.

Document the project

A public repository needs enough context for another person to understand the API. Generated caches and personal application records should remain on your computer.

  • Select the internship-tracker-api folder in the VS Code Explorer sidebar.
  • Click the new-file control beside the folder name.
  • Name the file .gitignore.
  • Define the local files that Git should exclude by pasting this content:
.venv/
__pycache__/
.pytest_cache/
*.pyc
applications.db

What does this file exclude?

  • The virtual environment stays local because another developer can recreate it from requirements.txt.
  • Python cache files stay out of the repository because they are generated automatically.
  • The pytest cache stays out of version history.
  • The applications.db file stays local so your internship records are not published.
  • Save .gitignore.
  • Confirm that .gitignore appears in the VS Code Explorer sidebar.

Is the file name incorrect?

Confirm that the file is named exactly .gitignore with no extra file extension.

Help me verify my project .gitignore file.

✔️ Awesome, I've got everything!

Your virtual environment, generated caches, and local database are excluded from version control.

ⓧ I'd like to double check the full code

.venv/
__pycache__/
.pytest_cache/
*.pyc
applications.db

How should I use this reference?

Compare these five lines with your saved .gitignore file. Each pattern should appear once.

The README.md file explains what the project solves. It also gives another Windows user a reproducible path to the interactive API documentation.

  • Select the internship-tracker-api folder in the VS Code Explorer sidebar.
  • Click the new-file control beside the folder name.
  • Name the file README.md.
  • Add the project summary and technology stack by pasting this first section:
# Internship Application Tracker API

A FastAPI backend for tracking internship applications by company, role, status, and notes. It demonstrates REST operations, request validation, SQLite persistence, automated tests, and Git-based delivery.

## Features

- Create and list internship applications
- Update and delete applications by ID
- Validate application status values
- Preserve records in SQLite after server restarts
- Test the API without a live network connection

## Stack

- Python 3.14.9
- FastAPI 0.159.1
- Pydantic 2.14.1
- Uvicorn 0.54.0
- SQLite
- pytest 9.1.1 and HTTPX 0.28.1

What does this section communicate?

The opening names the tracker problem and the backend skills demonstrated by the project. The feature list turns the implementation into capabilities that a reviewer can evaluate.

The stack records the exact versions used to build the API. This makes the setup reproducible.

  • Save README.md.
  • Confirm that the editor shows the project title followed by the Features section.
  • Confirm that the Stack section lists each pinned tool version.

Does the README look unstructured?

Check that the project title starts with one # character. Confirm that each section heading starts with two.

Help me check the first part of my Markdown README.

The next section gives a reviewer the exact local setup path. The endpoint table provides a compact map of the API.

  • Place the cursor below the HTTPX stack item in README.md.
  • Add the Windows instructions and endpoint table by pasting this section:
## Run locally on Windows

1. Create the environment: `python -m venv .venv`
2. Activate it in Command Prompt: `.venv\Scripts\activate.bat`
3. Install dependencies: `python -m pip install -r requirements.txt`
4. Start the server: `uvicorn main:app --reload`
5. Open `http://127.0.0.1:8000/docs`

## Endpoints

| Method | Path | Purpose |
| --- | --- | --- |
| GET | `/health` | Check service health |
| POST | `/applications` | Create an application |
| GET | `/applications` | List applications |
| PUT | `/applications/{application_id}` | Replace an application |
| DELETE | `/applications/{application_id}` | Delete an application |

Why include setup and endpoints?

The setup list lets another Windows user recreate the environment from the repository. The endpoint table shows which HTTP method matches each operation.

  • Save README.md.
  • Confirm that the editor shows a five-step Windows setup guide.
  • Confirm that the endpoint table contains the health operation and four application operations.

Is the endpoint table incomplete?

Confirm that the table has a header row and a separator row. Check that every endpoint has its own row.

Help me fix the Markdown endpoint table in my README.

The final section records the accepted status values and the test command. Its talking points help you explain the project's design choices during an internship interview.

  • Place the cursor below the DELETE endpoint row in README.md.
  • Complete the README by pasting this final section:
Valid status values are `saved`, `applied`, `interview`, `offer`, and `rejected`.

## Run tests

Run `pytest`. The test suite checks the health endpoint and an isolated SQLite create-and-list flow.

## Interview talking points

- Why an in-memory list loses data after a process restart
- How parameterized SQL separates values from query text
- How Pydantic models validate requests before database code runs
- Why tests use a temporary database instead of the development database

What does the final section add?

The status list documents the request constraint. The testing section gives reviewers one command for verifying the project.

The interview prompts connect the code to persistence, validation, and SQL safety decisions.

  • Save README.md.
  • Confirm that Run tests appears near the bottom of the file.
  • Confirm that all four interview talking points appear as separate list items.

Is part of the README missing?

Confirm that the accepted status values appear immediately after the endpoint table. Check that the testing section appears before the interview talking points.

Help me find the missing section in my README.

✔️ Awesome, I've got everything!

Your README now explains the project and gives reviewers a complete local setup path.

ⓧ I'd like to double check the full code

# Internship Application Tracker API

A FastAPI backend for tracking internship applications by company, role, status, and notes. It demonstrates REST operations, request validation, SQLite persistence, automated tests, and Git-based delivery.

## Features

- Create and list internship applications
- Update and delete applications by ID
- Validate application status values
- Preserve records in SQLite after server restarts
- Test the API without a live network connection

## Stack

- Python 3.14.9
- FastAPI 0.159.1
- Pydantic 2.14.1
- Uvicorn 0.54.0
- SQLite
- pytest 9.1.1 and HTTPX 0.28.1

## Run locally on Windows

1. Create the environment: `python -m venv .venv`
2. Activate it in Command Prompt: `.venv\Scripts\activate.bat`
3. Install dependencies: `python -m pip install -r requirements.txt`
4. Start the server: `uvicorn main:app --reload`
5. Open `http://127.0.0.1:8000/docs`

## Endpoints

| Method | Path | Purpose |
| --- | --- | --- |
| GET | `/health` | Check service health |
| POST | `/applications` | Create an application |
| GET | `/applications` | List applications |
| PUT | `/applications/{application_id}` | Replace an application |
| DELETE | `/applications/{application_id}` | Delete an application |

Valid status values are `saved`, `applied`, `interview`, `offer`, and `rejected`.

## Run tests

Run `pytest`. The test suite checks the health endpoint and an isolated SQLite create-and-list flow.

## Interview talking points

- Why an in-memory list loses data after a process restart
- How parameterized SQL separates values from query text
- How Pydantic models validate requests before database code runs
- Why tests use a temporary database instead of the development database

How should I use this reference?

Compare this complete reference with your saved README.md file. Check every heading, list, command, and table row in order.

Publish the repository to GitHub

A commit captures the tested project as one named point in its history. The remote repository makes that history available through a public link.

  • Switch back to the activated Command Prompt terminal in VS Code.
  • Initialize the repository and create its first commit by running these commands:
git init -b main
git add .
git commit -m "Build internship tracker API"

What do these commands create?

  • The first command initializes Git history on the main branch.
  • The second command stages the project files that are not excluded by .gitignore.
  • The final command records the staged files with the message Build internship tracker API.

You should see a commit summary in the terminal. The tested source code and documentation are now recorded in local Git history.

Did the commit fail?

Confirm that the terminal is using the internship-tracker-api folder.

If Git mentions missing author details, check the commit identity you configured during workspace setup.

Help me fix my first Git commit.

The GitHub repository is the destination for your local commit. Keep it empty because the required documentation already exists in the local project.

  • Return to the GitHub browser session from earlier.
  • Click New repository.
  • Enter internship-tracker-api as the repository name.
  • Choose Public for the repository visibility.
  • Leave the README option unselected.
  • Leave the .gitignore option unselected.
  • Leave the license option unselected.
  • Click Create repository.

You should see the empty internship-tracker-api repository. Its page provides the remote URL for your local project.

  • Copy the remote URL from the repository page.
  • Replace REMOTE-URL in the first command with the copied URL.
  • Configure the remote and display it by running these commands:
git remote add origin REMOTE-URL
git remote -v

What does the remote configuration do?

The first command stores the GitHub repository URL under the name origin. The second command displays the saved remote configuration.

You should see the repository URL for fetch operations. You should also see the same URL for push operations.

Is the remote URL incorrect?

Confirm that you replaced REMOTE-URL with the copied repository URL before running the command.

Check that the destination repository is named internship-tracker-api.

Help me diagnose my Git origin configuration.

The first HTTPS push can open a browser sign-in through Git Credential Manager. Your existing GitHub session lets you approve the connection without exposing a credential.

  • Push the committed main branch by running this command:
git push -u origin main

What does this push do?

The command uploads the local main branch to origin. It sets the remote branch as the upstream for future pushes.

  • Complete the Git Credential Manager browser sign-in if it appears.

You should see the push complete in the terminal. Your committed project is now available from the public repository.

Did the push stop at authentication?

Complete the browser confirmation opened by Git Credential Manager.

Confirm that the signed-in GitHub account owns the internship-tracker-api repository.

Help me diagnose my GitHub push.

Before you check the repository, which project files do you expect GitHub to show?

  • Return to the internship-tracker-api repository page.
  • Refresh the repository page.
  • Confirm that the selected branch is main.

You should see main.py, test_main.py, and requirements.txt on the repository page.

You should also see .gitignore and README.md. The local applications.db file should be absent.

That completes the publishing loop. Your tested API now has setup instructions and visible version history that another person can inspect.

Secret mission

Filter Applications by Status

Add a validated status filter to your published API. You will preserve the complete application list while giving recruiters an interview-only view.

Clean Up Your Resources

Clean Up Your Resources

Your tracker runs locally, so no cloud server or paid API remains running. GitHub Free stores the remote repository for $0 per month.

Resources you used:

  • Your local internship-tracker-api folder.
  • Your local .venv virtual environment inside internship-tracker-api.
  • Your local SQLite database in applications.db.
  • Your public internship-tracker-api repository on GitHub.

Keep everything running

No resource deletion is needed. Choose this if you want to keep developing the API or sharing its repository link.

  • Stop the local Uvicorn process by pressing Ctrl+C if it is still running.
  • Keep the internship-tracker-api folder so your code, virtual environment, tests, and local database remain available.
  • Leave the GitHub repository public so its link remains accessible.

Pause - I'll come back to this later

Making the repository private removes public access without deleting your commits. Your local project stays untouched for your return.

  • Stop the local Uvicorn process by pressing Ctrl+C if it is still running.
  • Keep the internship-tracker-api folder on your computer.
  • Return to the GitHub repository from earlier.
  • Select Settings.
  • Scroll to Danger Zone.
  • Select Change visibility.
  • Select I have read and understand these effects.
  • Select Make this repository private.

Delete - I don't want to use this again

Deleting these resources is permanent, so confirm that you no longer need your local database or published repository. This removes the project code and the stored application record.

  • Stop the local Uvicorn process by pressing Ctrl+C if it is still running.
  • Remove the local project from the VS Code Command Prompt terminal from earlier by running these commands:
deactivate
cd ..
rmdir /s /q internship-tracker-api

What do these commands remove?

  • The first line exits the virtual environment so its folder can be removed cleanly.
  • The second line moves the terminal to the folder that contains your project.
  • The final line permanently removes the entire internship-tracker-api folder, including .venv and applications.db.
  • Confirm that the internship-tracker-api folder no longer exists in its previous parent folder.

Delete the GitHub repository:

  • Return to the GitHub repository from earlier.
  • Select Settings.
  • Select General.
  • Scroll to Danger Zone.
  • Select Delete this repository.
  • Select I want to delete this repository.
  • Select I have read and understand these effects.
  • Enter internship-tracker-api when GitHub requests the repository name.
  • Confirm the permanent deletion.
  • Return to your GitHub repository list.
  • Confirm that internship-tracker-api is no longer listed.

Nice Work!

Nice Work!

You did it! You've built a persistent FastAPI internship application tracker with a tested GitHub repository ready for internship interviews.

What you learned:

  • Implemented validated CRUD operations through a FastAPI REST API. Used interactive documentation to exercise each endpoint.
  • Replaced volatile memory with SQLite persistence. Used parameterized SQL for database values. Returned clear HTTP 404 responses for missing application IDs.
  • Proved API behavior with isolated pytest tests. Documented local setup in README.md. Published the project with Git version history in a public GitHub repository.
  • Secret Mission: Added a validated status query parameter for focused application views. Verified /applications?status=interview before publishing the filter in a separate commit.

Ready to quiz yourself?