# Lektion 17 — Repository og databasen

Christian Budtz — [chbu@dtu.dk](mailto:chbu@dtu.dk)

---

## Program i dag

- Opsamling: lag i koden, `pg`, `$1` og `.env`
- Quiz: forberedelsen
- Gennemgang: repositoryet i `server.js`
- Quiz: repository og `GET`
- Øvelse 1: Listen husker
- Gennemgang: `POST`, fejl og formularen
- Quiz: `POST`, statuskoder og `$1`
- Øvelse 2: `POST` og testskemaet
- Øvelse 3: Gruppens handling

Pauser lægges ind undervejs.

---

## Læringsmål i dag

Efter lektionen skal du kunne:

- forklare, hvad et repository er, og hvorfor SQL'en samles ét sted
- forbinde `server.js` til gruppens Neon-database med connection-stringen i `.env`, uden for git
- hente og gemme med parametriseret SQL (`$1`, `$2`) og forklare, hvorfor værdierne ikke klistres ind i SQL-teksten
- svare `201`, `400` og `500` og teste dem fra `api.http`
- vise, at data overlever en genstart af serveren
- køre gruppens centrale brugerhandling fra formular til tabel og forklare, hvorfor browseren først sender et preflight

---

# Opsamling — forberedelsen

---

## Fra liste til tabel

Lektion 13: listen bor i et array i `server.js`. Lektion 15: de samme
patienter bor i en tabel i Neon. I dag flytter routen sine data fra arrayet
til tabellen.

```mermaid
flowchart LR
  subgraph laptop [Jeres bærbare]
    browser[Browser: mockup] -->|HTTP| route[Express-route]
    route -->|funktionskald| repo[Repository: patients.js]
  end
  subgraph neon [Neon]
    db[(Tabel patients)]
  end
  repo -->|SQL| db
```

Routen taler HTTP. Repositoryet taler SQL. Det er de tre lag fra lektion 11 —
nu som tre steder i koden.

---

## `pg` og `Pool`

`pg` er pakken, der sender SQL fra Node til PostgreSQL. En `Pool` holder et
par forbindelser åbne, så hvert request ikke skal logge ind forfra.

```js
const { Pool } = require("pg");
const pool = new Pool({ connectionString: process.env.DATABASE_URL });

const result = await pool.query("SELECT cpr, navn FROM patients ORDER BY id");
result.rows; // [{ cpr: "2512489996", navn: "Nancy Ann Test Berggren" }, …]
```

`pool.query` returnerer et objekt. Rækkerne ligger i `.rows` — et array af
objekter, samme form som listen i lektion 13.

---

## `$1` og et array

SQL-teksten har pladsholdere. Værdierne sendes ved siden af:

```js
await pool.query("SELECT navn FROM patients WHERE cpr = $1", [cpr]);
```

PostgreSQL får sætningen og værdien hver for sig. Værdien bliver aldrig en del
af SQL'en, uanset hvad den indeholder. En CPR, der ikke findes, giver ikke en
fejl — den giver et tomt array.

---

## `.env` og `--env-file`

Connection-stringen indeholder passwordet. Den står i `.env`, ikke i koden:

```text
DATABASE_URL=postgresql://neondb_owner:…@…neon.tech/neondb?sslmode=verify-full&channel_binding=require
```

```bash
node --env-file=.env server.js
```

`--env-file` lægger linjen i `process.env`, før koden kører. `.env` står i
`.gitignore`. Koden kan ligge på GitHub — passwordet kan ikke.

`sslmode=verify-full` i stedet for Neons `require` fjerner en lang
sikkerhedsadvarsel fra `pg`. Forbindelsen er krypteret i begge tilfælde.

---

# Quiz — forberedelsen

---

## Quiz!

Gå til [/quiz](/quiz) og indtast koden fra tavlen.

**Lektion 17: forberedelse** — route og repository, `pg`, `$1` og `.env`.

---

# Gennemgang — repositoryet i `server.js`

---

## Hvad flytter

I gruppens projekt:

```bash
npm install pg
```

`.env` med **gruppens** connection-string fra lektion 15, øvelse 3. `.env`
skrives ind i `.gitignore` ved siden af `node_modules/`. Tjek med
`git status`, at `.env` ikke står på listen.

Arrayet `patients` i `server.js` slettes. Den gamle `POST`-route, der lavede
`patients.push`, slettes også — den skriver I igen efter pausen.

---

## `patients.js`

En ny fil ved siden af `server.js`. Det er repositoryet.

```js
const { Pool } = require("pg");

const pool = new Pool({ connectionString: process.env.DATABASE_URL });

pool.on("error", (err) => {
  console.error("Databasen lukkede forbindelsen:", err.message);
});

async function hentPatienter() {
  const result = await pool.query("SELECT cpr, navn FROM patients ORDER BY id");
  return result.rows;
}

module.exports = { hentPatienter };
```

`module.exports` er det, andre filer kan hente med `require("./patients")`.

---

## Hvorfor `pool.on("error")`

Poolen holder forbindelser åbne til Neon. Mister den en af dem — netværket
falder ud, eller Neon lukker den — sender poolen en `error`-event. Uden en
lytter **crasher hele Node-processen**, og serveren dør, mens I kigger
væk.

Med lytteren skrives fejlen i terminalen, og poolen åbner en ny forbindelse
ved næste request.

---

## Routen bliver `async`

```js
const { hentPatienter } = require("./patients");

app.get("/api/patients", async (req, res) => {
  try {
    const patients = await hentPatienter();
    res.json(patients);
  } catch (err) {
    console.error(err);
    res.status(500).json({ error: "serverfejl" });
  }
});
```

Routen kender ingen SQL. Går databasen galt, svarer den `500` med JSON — og
skriver selve fejlen i terminalen, hvor I kan læse den. Brugeren skal ikke se
en stacktrace.

---

## Et `GET` gennem tre lag

```mermaid
sequenceDiagram
  participant Browser
  participant Route as Express-route
  participant Repo as patients.js
  participant Neon
  Browser->>Route: GET /api/patients
  Route->>Repo: hentPatienter()
  Repo->>Neon: SELECT cpr, navn FROM patients
  Neon-->>Repo: rækker
  Repo-->>Route: array af patienter
  Route-->>Browser: 200 og JSON
```

Tabellen i mockuppet er uændret. Den henter stadig fra
`http://localhost:3000/api/patients` — den ved ikke, at listen nu kommer fra
Neon.

---

# Quiz — repository og `GET`

---

## Quiz!

Gå til [/quiz](/quiz) og indtast koden fra tavlen.

**Lektion 17: repository** — `patients.js`, `async`, `500` og `.env`.

---

# Øvelse — listen husker

---

## Øvelse 1: Listen husker

Forbind `server.js` til gruppens database. `GET /api/patients` henter fra
tabellen. Tabellen i mockuppet viser de fem testpatienter. Genstart serveren —
de er der stadig.

Detaljerne står i [øvelsesarket](oevelser.md).

---

# Pause

---

# Gennemgang — `POST`, fejl og formularen

---

## `opretPatient`

I `patients.js`:

```js
async function opretPatient(cpr, navn) {
  const result = await pool.query(
    "INSERT INTO patients (cpr, navn) VALUES ($1, $2) RETURNING cpr, navn",
    [cpr, navn]
  );
  return result.rows[0];
}

module.exports = { hentPatienter, opretPatient };
```

`RETURNING` svarer med den række, der blev indsat. Routen kan sende den
tilbage uden et ekstra `SELECT`.

---

## `POST`-routen

```js
app.post("/api/patients", async (req, res) => {
  const { cpr, navn } = req.body ?? {};
  if (!cpr || !navn) {
    return res.status(400).json({ error: "cpr og navn skal med" });
  }
  try {
    const patient = await opretPatient(cpr, navn);
    res.status(201).json(patient);
  } catch (err) {
    console.error(err);
    res.status(500).json({ error: "serverfejl" });
  }
});
```

`400` tjekkes **før** databasen: klienten sendte for lidt. `500` er, når
databasen fejler: serveren kunne ikke. `201` er, når rækken er gemt.

---

## Hvorfor `$1` — SQL injection

Forestil jer, at navnet blev sat ind med `+`:

```js
// GØR IKKE DETTE
pool.query("INSERT INTO patients (cpr, navn) VALUES ('" + cpr + "', '" + navn + "')");
```

Navnet `x'); DELETE FROM patients; --` gør SQL'en til to sætninger. Den
anden sletter hele tabellen. Det er [Little Bobby Tables](https://xkcd.com/327/).

Med `$1` og `$2` er navnet bare tekst — også hvis det indeholder
anførselstegn og SQL. Alle værdier fra et request går gennem pladsholdere.

---

## Testskemaet

| # | Request | Forventet |
|---|---|---|
| 1 | `GET /api/patients` | `200` og listen |
| 2 | `POST /api/patients` med body | `201` og patienten |
| 3 | `POST /api/patients` uden body | `400` |
| 4 | `GET /api/patients` efter genstart | `200`, patienten fra 2 er der |
| 5 | `GET /api/patients` med forkert password i `.env` | `500` og `serverfejl` |
| 6 | `GET /findesikke` | `404` |

Samme `api.http` som i lektion 13 — statuskoden står øverst i svaret. Skemaet kommer i projektloggen med en kolonne for, hvad I
faktisk fik.

---

## Fra formularen: `cors`

Formularen i mockuppet sender `POST` med JSON. Før det sender browseren et
`OPTIONS`-request (preflight) og spørger, om det er tilladt. Den hjemmelavede
header fra lektion 13 svarer ikke på det.

```bash
npm install cors
```

```js
const cors = require("cors");
app.use(cors());
```

`cors()` erstatter jeres egen `Access-Control-Allow-Origin`-middleware og
svarer på preflight. Den skal stå før jeres routes. Det er samme pakke som
underviserens API bruger.

---

## Formularen

Samme mønster som login i lektion 7:

```js
form.addEventListener("submit", async (event) => {
  event.preventDefault();
  fejl.hidden = true;
  try {
    const res = await fetch("http://localhost:3000/api/patients", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        cpr: document.getElementById("cpr").value,
        navn: document.getElementById("navn").value
      })
    });
    if (!res.ok) {
      fejl.hidden = false;
      return;
    }
    form.reset();
  } catch (err) {
    fejl.hidden = false;
  }
});
```

`!res.ok` fanger `400` og `500`. `catch` fanger, at serveren slet ikke
svarer. Network viser `OPTIONS` med `204` og derefter `POST` med `201`. Hele
uddraget med HTML står i [øvelsesarket](oevelser.md).

---

# Quiz — `POST`, statuskoder og `$1`

---

## Quiz!

Gå til [/quiz](/quiz) og indtast koden fra tavlen.

**Lektion 17: POST** — `201`, `400`, `500`, `$1` og `cors`.

---

# Pause

---

# Øvelser — `POST` og gruppens handling

---

## Øvelse 2: `POST` og testskemaet

`opretPatient` i repositoryet og `POST` i routen. Kør de seks rækker i
testskemaet fra `api.http`, og skriv resultatet i projektloggen.

Detaljerne står i [øvelsesarket](oevelser.md).

---

## Øvelse 3: Gruppens handling

Gruppens egen tabel fra lektion 15 får sit eget repository og en
`POST`-route. En formular i mockuppet sender `POST`, og rækken lander i
tabellen i Neon. Det er den centrale brugerhandling til D2.

Detaljerne står i [øvelsesarket](oevelser.md).

---

## Efter timen: D2 og deploy

D2 afleveres **tirsdag 10/11**: den centrale brugerhandling skal virke hele
vejen fra formular til tabel — på en URL på Vercel, ikke kun på jeres
maskine.

Deploy er det samme Vercel-projekt som i lektion 9, nu med en server:

1. Frontend flytter til `public/`, og `fetch` bruger `/api/…`
2. Ejeren vælger **Express** som Framework Preset og sætter `DATABASE_URL`
3. Ejeren pusher — og den centrale handling testes på den live URL

Trin for trin: [Deploy backend til Vercel](deploy.md). Opgaveoplægget til D2
ligger på DTU Learn.
