# Data - SSR websites

> Reading and writing data in the pages of an SSR website - loaders, ctx.db with the tables a website lists and the row rules of each, ctx.api as the visitor, ctx.sys, ctx.fetch, ctx.cache and JSON files.

Source: https://docs.apimaker.dev/v4/docs/ssr/data.html

A page reads its data on the server, in its `loader`, next to the database. The browser gets HTML : no API of yours is called from it, no token of yours travels to it.

```tsx
export async function loader(ctx: PageContext) {
    const [products, categories] = await Promise.all([
        ctx.db.query({ instance: 'mongodb', database: 'shop', table: 'products', find: { active: true }, sort: { name: 1 }, limit: 24 }),
        ctx.db.distinct({ instance: 'mongodb', database: 'shop', table: 'products', distinctField: 'category' }),
    ]);
    return { products, categories };
}

export default function Products({ data }: PageProps<typeof loader>) { … }
```

- The loaders of the layouts and of the page run at the same time ; `Promise.all` reads several things at once inside one.
- What a loader returns may be dates, ids, numbers : the page gets them as they are.
- A loader may answer instead of the page : `redirect('/login')`, `notFound()`, or a `Response` (`ctx.json(...)`).

## Four ways to reach data

| | What it reaches | As whom |
|---|---|---|
| `ctx.db` | The tables `site.config.ts` lists in `data.read` and `data.write`, with the row rule of each | The website, under its rules |
| `ctx.api` | The APIs of the account : tables (`ctx.api.db`) and custom APIs (`ctx.api.customApi`) | The visitor, as a request of its browser would be : the access of each API, its hooks |
| `ctx.sys` | `g.sys` : everything the account may do | The account. Off unless `data.allowSys` turns it on |
| `ctx.fetch` | Another server | The hosts `data.fetch` lists |

## `ctx.db` : the tables a website lists

`ctx.db` takes the words of [`g.sys.db`](https://docs.apimaker.dev/v4/docs/pre-defined-terms/global-object-g.html) : `getAll`, `getById`, `query`, `count`, `distinct`, `distinctQuery`, `aggregate`, `saveSingleOrMultiple`, `masterSave`, `arrayOperations`, `updateById`, `updateMany`, `replaceById`, `removeById`, `removeByQuery`, and `ctx.db.gen.*` for tables without a schema. A call names its `instance`, `database` and `table`.

**A website reaches no table until `site.config.ts` lists it.** A call to any other table is refused with `403`, whatever the code asks :

**site.config.ts**

```ts
export default defineSite({
    data: {
        read: [
            'mongodb/shop/products',
            // A rule : the columns a read gives.
            { table: 'mongodb/shop/categories', fields: ['_id', 'name', 'slug'] },
            // The rows of the visitor : added to every query, update and removal.
            { table: 'mongodb/shop/orders', where: { customer_id: '$user._id' }, requireUser: true },
        ],
        write: [
            { table: 'mongodb/shop/orders', where: { customer_id: '$user._id' }, requireUser: true, allow: ['create'] },
            // A sign-up : a visitor may add a customer, never change nor remove one ; the new one is a customer.
            { table: 'mongodb/shop/customers', allow: ['create'], set: { groups: ['customer'] } },
        ],
    },
});
```

| Part of a rule | What it does |
|---|---|
| `table` | `instance/database/table`, as the account names them. `instance/database/*` for every table of a database. |
| `where` | Added to the conditions of every read, update and removal (`$and`). `'$user.<field>'` is a field of the visitor who signed in. |
| `set` | What every row the website saves gets, over what the page sent. `where`'s equalities by default : with `where: { customer_id: '$user._id' }`, every saved order is the visitor's. |
| `requireUser` | Only for a visitor who signed in (`401` for the others). A rule which names `$user` asks for one anyway. |
| `fields` | The columns a read gives : a `select` of the page is kept to them. Give one to a table of users, without its password. |
| `allow` | For `data.write` : `create`, `update`, `delete`. All three by default. |

What the server checks :

- A call by id (`getById`, `updateById`, `replaceById`, `removeById`) finds the row, and checks it against the rule : a row of another visitor is not found (`404`).
- `deep` reads other tables : each needs a rule of `data.read`. So does a path through a column which refers to another table (`customer_id.email` in `select`, `find` or `sort`).
- `$user._id` is a value of the visitor's row, as a text (an `ObjectId` of MongoDB is its hex text).
- A visitor without the field a rule names (`$user.customer_id` for a user with none) reaches nothing (`403`).

> **Warning : What a visitor sent is a value, never a query**
>
> Build `find` from what the visitor sent as values : `find: { category: String(ctx.query.category) }`. Never hand an object of the query or of a form to the database as it came : `{ $where: … }` or `{ $ne: null }` would be operators. For a search, escape the text of a regular expression : `{ name: { $regex: text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), $options: 'i' } }`.

## `ctx.api` : as the visitor

`ctx.api.db` takes the same words as `ctx.db`, but calls the APIs of the account as the visitor would : the access of each API (its groups, the tokens it asks for), the API user of the website (`data.apiUser`), the tokens of the visitor its auth providers gave, and the hooks of the API. Use it when the APIs already say who may do what.

```ts
// A custom API of the account, with the token of the visitor.
const quote = await ctx.api.customApi('/shop/quote', { method: 'POST', body: { items } });
const orders = await ctx.api.db.query({ instance: 'mongodb', database: 'shop', table: 'orders', limit: 10 });
```

## `ctx.sys` : everything the account may do

`ctx.sys` is `g.sys` of your code : secrets, Redis, events, every table. It is off : `data.allowSys: true` in `site.config.ts` turns it on. A page with `ctx.sys` reads whatever its code asks, so check every value a visitor sends.

```ts
const key = await ctx.sys.system.getSecret('stripe.apiKey');
```

## `ctx.fetch` : other servers

**site.config.ts**

```ts
data: { fetch: ['api.github.com', '*.example.com'] },
```

```ts
const answer = await ctx.fetch('https://api.github.com/repos/apimaker/docs', { headers: { accept: 'application/json' }, timeout: 5000 });
const stars = answer.json?.stargazers_count;
```

`ctx.fetch` returns `{ status, headers, body, json }`. A host which is not listed is refused. Answers are 10 MiB at most ; a request waits 30 seconds at most (`timeout` in milliseconds asks for less).

## `ctx.cache` : values kept between requests

The cache of the website, in Redis, its keys its own :

```ts
const rates = await ctx.cache.getOrSet('rates', 600, () => ctx.fetch('https://api.example.com/rates').then((answer) => answer.json));
await ctx.cache.set('banner', 'Free delivery today', 3600);
await ctx.cache.remove('banner');
```

For whole pages, see [caching](https://docs.apimaker.dev/v4/docs/ssr/caching.html).

## JSON files

A file of JSON of the website is a module : `import posts from '@/data/posts.json'`. It is read when the website is built, and shared by every request : sample data, settings, translations.

## What not to do

- Never keep `ctx`, a visitor's data or a row in a variable of a module : the module is shared by every visitor of the engine.
- Never write `$user` values into a page kept for everyone (`cache.scope: 'public'`) : the server does not keep a page which read cookies or headers, but it can not know what a loader wrote.
