Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 12 additions & 35 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,35 +32,7 @@ http://it2810-03.idi.ntnu.no/project2

## Architecture

See [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) for how the frontend, backend and database fit together, and how the code is organised.

## API and database

### Plant data: the Perenual API

The plant data comes from the [Perenual API](https://perenual.com/docs/api), which has information about thousands of plant species, including images, care information and plant traits.

We do **not** call Perenual from the app while it is running. Instead, the script [backend/seedDataFetch.js](backend/seedDataFetch.js) has fetched the data once, cleans it and stored it in our own MongoDB database on the VM. We have keept the seedDataFetch file so you can see how this has been done. The app only talks to our own GraphQL backend. We chose this approach for three reasons:

- **Rate limits:** The free tier on the Perenual API allows about 100 requests per day, and only returns details for plants with an ID up to 3000. If the app called Perenual directly, a handful of users would use up the daily limit in minutes. This is therfore a scalable solution for using this API source as the source for our data.
- **Control over the data:** We only keep the fields we use, in the format we want. That keeps responses small, and makes searching, filtering, sorting and pagination something we decide on ourselves instead of something limited by what Perenual supports.
- **Reliability and security:** The app keeps working even if Perenual is down or changes its API, since the data is stored in our own database.

### Backend: GraphQL with Apollo Server

The backend exposes a single GraphQL endpoint. With GraphQL, the frontend asks for exactly the fields each view needs. A plant card needs more fields than a list item, and both are served by the same `Plant` type without extra endpoints. The schema also gives us one typed contract between frontend and backend.

### Database: MongoDB

We store the plants in MongoDB, a document database. It is a good fit for this data:

- **The data is already documents.** Perenual returns JSON with nested objects and lists (such as sunlight requirements and scientific names). Each plant becomes one document with almost the same shape, so there is no need to split it into several tables and join them back together. This limits the cleaning necessary.
- **Plants have different fields.** Some plants have information that others are missing. A document database handles optional fields naturally, without a lot of empty columns.
- **It is simple to update.** The seed script first inserts a basic entry for each plant, and later adds details to the same document (an upsert keyed on `perenualId`).

The trade-off in using a NoSQL databse such as MongoDB commpared to a relational database such as MySQL is that it does not enforce relationships between collections the way a relational database does. That matters little here, because the plant collection is mostly read-only, and user data such as saved matches only needs to refer to a plant by its ID.

MongoDB runs on the course VM and only listens on `127.0.0.1`, so it cannot be reached directly from the internet. During development, you reach it through an SSH tunnel (see below).
See [architecture.md](docs/architecture.md) for how the frontend, backend and database fit together, and how the code is organised. It also describes the plant data and the Perenual API, why we chose GraphQL and MongoDB, and how the data flows from the API to the frontend.

## Run the project locally

Expand Down Expand Up @@ -91,14 +63,14 @@ The default values work as they are. `.env` is ignored by git and must never be

### 3. Open an SSH tunnel to the database (in PC terminal window)

Open a **separate terminal**, replace `<ntnu-username>` with your username and run:
Open a **separate terminal**, replace `<ntnu-username>` with your username and run the command below.

After you enter your password, the command looks like it hangs. That means the tunnel is open. Keep the terminal open while you work, and stop the tunnel with `Ctrl+C` when you are done.

```bash
ssh -N -L 27017:localhost:27017 <ntnu-username>@it2810-03.idi.ntnu.no
```

After you enter your password, the command looks like it hangs. That means the tunnel is open. Keep the terminal open while you work, and stop the tunnel with `Ctrl+C` when you are done.

### 4. Start the app (in the VSC terminal window)

From the repo root:
Expand All @@ -107,7 +79,7 @@ From the repo root:
npm run dev
```

This starts both the backend (http://localhost:4000) and the frontend. Open http://localhost:5173/project2/ in your browser. The frontend forwards `/graphql` requests to the backend, so both must be running.
This starts both the backend (http://localhost:3001) and the frontend. Open http://localhost:5173/project2/ in your browser. The frontend forwards `/graphql` requests to the backend, so both must be running.

See [backend/README.md](backend/README.md) for more details about the backend setup (in Norwegian).

Expand All @@ -118,7 +90,7 @@ The project has three `package.json` files: one in the repo root, one in `backen
| Command | What it does | When to use it |
| --------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------- |
| `npm run install:all` | Runs `npm install` in the root, `backend` and `frontend`. | After cloning, and after pulling changes to any `package.json`. |
| `npm run dev` | Starts the backend (`tsx watch`, port 4000) and the frontend (Vite, port 5173) in the same terminal with [concurrently](https://www.npmjs.com/package/concurrently). Output is prefixed with `[backend]` (blue) and `[frontend]` (green). Both restart or reload automatically when you save a file. Stop both with `Ctrl+C`. | Day-to-day development. The SSH tunnel must be open first. |
| `npm run dev` | Starts the backend (`tsx watch`, port 3001) and the frontend (Vite, port 5173) in the same terminal with [concurrently](https://www.npmjs.com/package/concurrently). Output is prefixed with `[backend]` (blue) and `[frontend]` (green). Both restart or reload automatically when you save a file. Stop both with `Ctrl+C`. | Day-to-day development. The SSH tunnel must be open first. |
| `npm run build` | Compiles the backend with `tsc` to `backend/dist`, then type checks and builds the frontend with Vite to `frontend/dist`. Stops at the first error. | To check that everything compiles, and before deploying. |
| `npm run lint` | Runs ESLint in `backend`, then in `frontend`. | Before you push or open a pull request. |
| `npm test` | Runs the frontend tests once with Vitest and exits. | Before you push or open a pull request. |
Expand All @@ -139,7 +111,7 @@ You can also run scripts in one part of the project only, either with `cd backen

## Testing

See [frontend/src/tests/README.md](frontend/src/tests/README.md) for what we test, how the tests are organised and how to run them.
See [tests.md](docs/tests.md) for what we test, how the tests are organised and how to run them.

To run all tests from the repo root:

Expand All @@ -151,6 +123,11 @@ Lint, tests and build also run automatically on every pull request through GitHu

TODO! The testing coverage is:

## Quality and design

See [quality.md](docs/quality.md) for our design choices, accessibility considerations, sustainable web development
and work practices.

## Known issues

TODO!
7 changes: 0 additions & 7 deletions backend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

92 changes: 92 additions & 0 deletions docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# Architecture

This document describes how the data flows from the API to the frontend, how the frontend, backend and database fit together. It also describes the plant data and the Perenual API and why we chose GraphQL and MongoDB.
The folder structure is shown at the end of this document.

## Data flow

```text
Perenual API → seedDataFetch.js → MongoDB → Apollo Server (GraphQL) → Frontend
```

The plant data is fetched from the Perenual API once and stored in our own database. The frontend never talks to the database directly. All data goes through the GraphQL API in the backend:

1. The frontend sends a GraphQL request to the backend (for example the `getPlants` query with `limit` and `offset`).
2. Apollo Server runs the matching resolver, which reads from the `plants` collection in MongoDB.
3. The resolver maps the database documents to the `Plant` type (for example `commonName` becomes `name`) and returns only the fields the frontend asked for.
4. The frontend receives the data as JSON and renders it.

The sections below describe each part in more detail.

## Frontend: React, TypeScript and Apollo Client

The frontend is built with React and TypeScript (Vite).

- **Routing:** Routes are defined in `AppRoutes.tsx` with React Router, and the app is served under the base path `/project2`.
- **Reusable logic in hooks:** Logic without UI is kept in hooks. `useDebounce` delays updates while the user is typing, and `useSessionState` stores state in `sessionStorage`.
- **Internationalisation:** Text is stored in translation files for Norwegian and English (i18next).
- **Shared types and helpers:** `Plant` and `Preference` are defined in `types/`, and helper functions such as `isPreferenceMatch` in `utils/`.

### Communication with the backend

The frontend uses Apollo Client, which is set up in `main.tsx` with an in-memory cache. During development, requests to `/graphql` are proxied by Vite to the backend on port 3001 (see [vite.config.ts](../frontend/vite.config.ts)).

## Backend: GraphQL with Apollo Server

The backend exposes a single GraphQL endpoint. With GraphQL, the frontend asks for exactly the fields each view needs. A plant card needs more fields than a list item, and both are served by the same `Plant` type without extra endpoints. The schema also gives us one typed contract between frontend and backend.

- `db.ts` connects to MongoDB using the connection string in `.env` (see `.env.example`).
- `server.ts` defines the schema and resolvers. The `getPlants(limit, offset)` query returns plants with pagination. The resolver maps database documents to the GraphQL type (for example `commonName` becomes `name`), so the database structure is not exposed to the frontend.
- `seedDataFetch.js` fills the database with plant data (see [Data source](#data-source-the-perenual-api) below).

## Database: MongoDB

We store the plants in MongoDB, a document database. It is a good fit for this data:

- **The data is already documents.** Perenual returns JSON with nested objects and lists (such as sunlight requirements and scientific names). Each plant becomes one document with almost the same shape, so there is no need to split it into several tables and join them back together. This limits the cleaning needed.
- **Plants have different fields.** Some plants have information that others lack. A document database handles optional fields naturally, without many empty columns.
- **It is simple to update.** The seed script first inserts a basic entry for each plant, and later adds details to the same document (an upsert keyed on `perenualId`).

The trade-off in using a NoSQL database such as MongoDB compared to a relational database such as MySQL is that it does not enforce relationships between collections the way a relational database does. That matters little here, because the plant collection is mostly read-only, and user data such as saved matches only needs to refer to a plant by its ID.

MongoDB runs on the course VM and only listens on `127.0.0.1`, so it cannot be reached directly from the internet. During development, it is reached through an SSH tunnel (see [Run the project locally](../README.md#run-the-project-locally) in the README).

## Data source: the Perenual API

The plant data comes from the [Perenual API](https://perenual.com/docs/api), which has information about thousands of plant species, including images, care information and plant traits.

The app does **not** call Perenual while it is running. Instead, the script [backend/seedDataFetch.js](../backend/seedDataFetch.js) fetched the data once, cleaned it and stored it in our own MongoDB database on the VM. We have kept the seedDataFetch file so you can see how this has been done. The app only talks to our own GraphQL backend. We chose this approach for these reasons:

- **Rate limits:** The free tier on the Perenual API allows about 100 requests per day, and only returns details for plants with an ID up to 3000. If the app called Perenual directly, a handful of users would use up the daily limit in minutes. By fetching the data once and serving it from our own database, the number of users no longer affects the number of requests to Perenual.
- **Fewer requests:** Because the data is fetched only once, and the raw responses are cached locally so the script never requests the same data twice, we avoid unnecessary network traffic and resource use.
- **Control over the data:** We only keep the fields we use, in the format we want. That keeps responses small, and makes searching, filtering, sorting and pagination something we decide on ourselves, instead of being limited by what Perenual supports.
- **Reliability:** The app keeps working even if Perenual is down or changes its API, since the data is stored in our own database.

## Folder structure

```text
T03-Project-2/
├── .github/ # CI workflow (lint, tests, build) and CODEOWNERS (required PR review)
├── backend/
│ ├── src/
│ │ ├── db.ts # MongoDB connection (MongoDB driver, configured via .env)
│ │ └── server.ts # Apollo Server with GraphQL schema and resolvers (getPlants query)
│ ├── tests/ # Backend tests (none written yet)
│ ├── seedDataFetch.js # Script that fetches plant data from the Perenual API and stores it in MongoDB
│ └── .env.example # Template for required environment variables
├── docs/ # Project documentation (architecture, tests, ...)
├── frontend/
│ ├── src/
│ │ ├── assets/ # Static images
│ │ ├── components/ # Reusable UI components, each in its own folder with a matching .css file
│ │ ├── hooks/ # Reusable logic without UI (useDebounce, useSessionState)
│ │ ├── i18n/ # Translation setup and locale files (no, en)
│ │ ├── pages/ # One folder per page/route with a matching .css file
│ │ ├── tests/ # Unit and component tests, including test setup (see tests.md)
│ │ ├── types/ # Shared TypeScript types (Plant, Preference)
│ │ ├── utils/ # Helper functions (e.g. matching plant traits against user preferences)
│ │ ├── AppRoutes.tsx # Route definitions
│ │ └── main.tsx # Application entry point
│ └── vite.config.ts # Vite and Vitest configuration, including proxy to the backend
└── README.md # Project overview and how to run the application
```
10 changes: 10 additions & 0 deletions docs/ai-usage.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# AI usage

AI was used as an assistant throughout the development of the web application. It was mainly used for:

- **Setup and configuration** — setting up the project in VS Code, Vitest, and project structure with backend and database
- **Explaining technical concepts** — how the database, backend and frontend are related, and how to implement debounce
- **Writing and reviewing tests** — drafting test files with mocked dependencies, and explaining the reasoning behind test structure and snapshot testing. AI also came up with suggestions regarding what should be tested to get a better test coverage
- **Writing documentation** — drafting and refining documentation and comments in the code

All AI-generated suggestions were reviewed, tested, and adapted by the team before being committed. Design and architectural decisions were made by the team.
Loading
Loading