1/21 Setup, Mono-Repos, & Backend-for-Frontend

GitHub Classroom: https://classroom.github.com/a/NwOUKg6L

It’s recommended you create a separate folder to keep lab repositories in. You may find it useful to keep a separate instance of the jarvis repository as it will be continually updated throughout the semester.

If you’ve ever worked at a company that has a sizable codebase before, odds are you’ve most likely dealt with a mono-repo. A mono-repo is a single git repository that contains most if not all of a companies code. Why do so many organizations have a mono-repo? We’ll discuss the motivations and philosophy behind this, and various topics we cover in the future will additionally build on this.

A Lay of the Land

Typically, there’s a few ways that organizations structure their code: multi-repo, mono-repo, or monolith. A multi-repo setup is where each service is split into its own repository, a mono-repo is a single repository that contains all services, and a monolith is a single service that encapsulates all functionality.

I won’t go too much in detail about each of these since there’s a good amount of posts online about this topic, but it’s important to note that none of these are a “golden bullet”. That is, each approach has its pros and cons depending on your codebase structure, company size, and development cycle. Generally, though, monoliths have fallen out of favor for larger teams and codebases since they do not scale well. Imagine our Jarvis service as a monolith: we would have everything running as a single service. If for some reason the chat feature was heavily used, there would be no way to scale just that portion of our product.

Between a multi-repo and mono-repo setup, I personally favor a mono-repo. Most of the largest tech companies in the world favor mono-repos, too: Google, Meta, Airbnb, and Uber. The main benefits over a multi-repo are that code reusability is encouraged, dependency management is easier, and doing things cross-service is easier. For example, if you needed to write a feature that required changes in two different services, you could do that in a single git commit in a mono-repo. Additionally, things like integration tests are easier to write if all code is in the same repository (which we’ll revisit when we look at CI/CD). However, a mono-repo can become complex to navigate/understand, and can take a looong time to initially clone (Google’s monorepo is hundreds of terabytes!!).

Installation

Clone the repository created by GitHub classroom for you. This is a snapshot of the jarvis-monorepo, which will be continually updated throughout the course. You’ll see that there’s two folders: backend/ and frontend/, which (surprise surprise), contain the code for the backend and frontend. We’ll be using FastAPI for our backends and next.js for our frontend. Let’s get this running locally!

To get started, we’ll need to install uv, a package manager for Python. You can install it and get familiar with the basic usage on the documentation.

Running Locally (uv Introduction)

Change directory to the backend/services/auth/ folder and run

uv sync

What this command does is determine what packages are needed (defined in auth/pyproject.toml), compares them to what we currently have installed, and “syncs” them, e.g. determines if the versions we want match the constraints in the pyproject.toml.

Now that our packages are synced, we can run the application. If you’ve used Python before, uv makes things a bit different. Instead of python ..., we run uv run .... This is because uv also manages the Python versions on our machine. The uv run command looks at the .python-version file to determine which Python binary to use. If you’ve used Python before, this is a huge benefit. You no longer have to constantly switch virtual environments as you work on different projects.

To run the auth service execute:

uv run fastapi dev main.py

You should see logs along the lines of

    ...
    server   Server started at http://127.0.0.1:8000
    server   Documentation at http://127.0.0.1:8000/docs

       tip   Running in development mode, for production use: fastapi run

             Logs:

      INFO   Will watch for changes in these directories: ['/Users/davidcao/work/teaching/jarvis-monorepo/auth']
      INFO   Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
      INFO   Started reloader process [40027] using WatchFiles
      INFO   Started server process [40029]
      INFO   Waiting for application startup.
      INFO   Application startup complete.

Jarvis’ Mono-Repo Structure

We’ll be adopting a mono-repo structure for Jarvis. We currently have two services, auth and notes. Each of these is managed as its own uv project under backend/services/<service>/. This means that each service can manage its own dependencies, e.g. the pyproject.toml files. Additionally, this allows us to have some shared code into a backend/utilities/ folder. This is also its own uv project, but is built as a dependency. In its pyproject.toml file, this is the lines

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

which says to use the hatchling tool to build our package. Then, you can see how each service imports the utilities as a package in their pyproject.toml:

[tool.uv.sources]
shared = { path = "../../utilities", editable = true }

This tells uv that we have a package that should come from ../../utilities called shared. We then import shared in the dependencies list. Finally, we can import code from the shared package as we would with any other package, e.g. from shared.database import Base.

Right now, this structure might seem over-complex/unwieldy, but as we’ll see throughout the course, this codebase structure can make our lives much easier later down the line. Eventually, Jarvis will be a system that runs with 3 services, of which each will have nodes with different roles. A mono-repo structure will help make these distinctions clear.

Jarvis Authentication Flow

Let’s go over how Jarvis is doing authentication, as understanding it will help debug network requests you may need to deal with in the future. The auth service is responsible for maintaining user information as well as logging in. How would something like the notes service authenticate requests? There are quite a few paradigms, but we’ll be using Bearer tokens. Any authenticated requests will expect an HTTP header Authorization: Bearer <token>. This token is the token that is issued from the auth service. Each service will then make a network request to the auth service to determine if the provided token is valid or not.

  %%{init: {'theme':'dark'}}%%
sequenceDiagram
    participant Client
    participant NotesService as Notes Service
    participant AuthService as Auth Service

    Client->>NotesService: GET /notes
    NotesService->>AuthService: GET /validate
    AuthService-->>NotesService: UserResponse
    NotesService-->>Client: Notes data (200 OK)

Let’s test this! We’ll need to open two terminals, one to spin up the auth service and one to spin up the notes service. Run the following commands:

# from backend/services/auth
uv run fastapi dev main.py --port 8000
# from backend/services/notes
uv run fastapi dev main.py --port 8001

First, we’ll need to get a token from the auth service. Navigate to localhost:8000/docs. This is a built in UI that FastAPI provides by default (nice, right?). Let’s create a user by executing signup/. Then, use the /token endpoint to get an access_token. You’ll just need to fill out the username and password fields, you can leave the rest as is.

Unfortunately, since the auth and notes service are two separate FastAPI applications, there’s no nice built in way to have the token used automatically. Instead, we’ll need to manually make the request using something like curl or Postman. Using curl, we can hit the/notes endpoint like so

curl --location --request GET 'localhost:8001/notes' \
--header 'Authorization: Bearer <token>'

You should see a response of simply [], since we don’t have any notes yet. Let’s create a note:

curl --location --request POST 'localhost:8001/notes' \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data-raw '{
    "title": "My note",
    "content": "Some content"
}'

# response
{"id":1,"user_id":1,"title":"My note","content":"Some content","created_at":"2025-09-03T20:37:19.335101","updated_at":"2025-09-03T20:37:19.335109"}

Now, if we get /notes again, we should see the note we just created. Great!

Jarvis Frontend

That was fun and all, but if we have to do that every time we want to test any authenticated endpoint, it’s going to get old fast. You’ll notice that there’s a frontend/ folder in the repo as well. This is written in next.js as a single-page-application with a backend-for-frontend. Specifically, any requests from the frontend are made directly to the next.js server, which then proxies them to the backend. We discussed a few of the reasons why this paradigm might be good in lecture, but one large benefit with our OAuth2 setup is that the browser does not need to store any sensitive credentials or tokens. Instead, the BFF handles token management for us by using server-side cookies. You don’t need to be worried about this is done technically, but it does mean in the future we will need to deploy our frontend as a server. So, technically, the authentication flow from above looks like:

  %%{init: {'theme':'dark'}}%%
sequenceDiagram
    participant Client
    participant BFF as BFF
    participant NotesService as Notes Service
    participant AuthService as Auth Service

    Client->>BFF: GET /notes
    BFF->>BFF: Token from cookies
    BFF->>NotesService: GET /notes
    NotesService->>AuthService: GET /validate
    AuthService-->>NotesService: UserResponse
    NotesService-->>BFF: Notes data
    BFF-->>Client: Notes data (200 OK)

You can specifically see the routes we have defined at frontend/app/api/ if you’re interested. The paths under auth/ are the handlers for the OAuth2 flow, and the router under proxy/[...slug]/ handles any request of the form /proxy/....

Now, let’s get the frontend up and running. First, install the latest version of Node.js if you don’t have it already. Then, navigate to the frontend/ folder and run

npm install

This will install the various packages needed for the frontend, similar to how uv does it for our backend services. Once this is done, you can run the frontend server with

npm run dev

Navigate to localhost:3000 in your browser and you should see a login page. Login with the credentials you created (or sign a new user up). You should then be able to play around with the notes features. Try creating, editing, and deleting some notes. The chat feature will not work as we don’t have a chat service yet.

Submission

There’s no extra work to submit for this lab since it’s the first one. Typically, you’ll simply commit and push the changes you made during the lab for submission. For this lab, simply change the content in the README.md to include your pennkey. Make sure to commit and push your changes to the repo created for you through the GitHub classroom link. This will be how we track lab submissions in the future.


The philosophy behind mono-repos and an introduction to Jarvis.

2026-01-21