# TypeScript and JavaScript

> Write custom APIs, hooks, schedulers and event listeners of API Maker in TypeScript, run by Node.js or Bun. async function main(g), g.req, g.res, g.sys.db, utility classes, npm packages, with many examples.

Source: https://docs.apimaker.dev/v4/docs/languages/typescript.html

Code in TypeScript is compiled to JavaScript when you save it, and runs on the runtime you choose in [Sandbox Settings](https://docs.apimaker.dev/v4/docs/settings/sandboxSettings.html) : **Node.js** (the default) or **Bun**, in the version you pick there, with only the npm packages you add.

> **In short**
>
> - Write `async function main(g)` and export it : `module.exports = main`.
> - It runs on Node.js (24) or Bun (1), as [Sandbox Settings](https://docs.apimaker.dev/v4/docs/settings/sandboxSettings.html) say.
> - Refuse a request with `throw [{ code: 400, message }]`.

**hello.ts**

```typescript
import * as T from 'types';

async function main(g: T.IAMGlobal) {
    return { hello: g.req.body.name || 'world' };
}
module.exports = main;
```

| | Node.js | Bun |
|---|---|---|
| Image | `node:<version>-slim` (24) | `oven/bun:<version>-slim` (1) |
| Oldest version | 20 | 1.0 |
| Packages | `npm install` | `bun add` |
| Code coverage of test cases | yes | no : Bun has no V8 profiler |
| Downloads while code runs | none | none (automatic install off) |

The code, the [global object `g`](https://docs.apimaker.dev/v4/docs/pre-defined-terms/global-object-g.html), utility classes, `types` and the [examples of `g.sys`](https://docs.apimaker.dev/v4/examples/index.html) are the same on both : switching the runtime needs no change of code, unless the code uses something only one runtime has (`Bun.file`).

## The request and the answer

**Read the request, shape the answer**

```typescript
import * as T from 'types';

async function main(g: T.IAMGlobal) {
    const status = g.req.query.status || 'open';
    const signed = !!g.req.headers['x-am-authorization'];
    g.res.statusCode = T.EStatusCode.CREATED;
    g.res.headers = { 'X-Request-Kind': 'orders' };
    return { customer: g.req.params.id, status, signed };
}
module.exports = main;
```

### Bytes as the answer

A file, a picture, a PDF : return its bytes (a `Buffer`, or any `Uint8Array` : `Buffer.from(await answer.arrayBuffer())` for what `fetch` downloaded), or put them in `g.res.output`, and name their kind of content. The answer is those bytes as they are, whatever the kind of content, with the headers of the code. (A text under a kind of bytes, `image/gif`, is read as base64.)

**A picture as the answer**

```typescript
import * as T from 'types';

async function main(g: T.IAMGlobal) {
    const picture = Buffer.from('R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOwA=', 'base64');
    g.res.contentType = 'image/gif' as T.EContentType;
    g.res.headers = { 'Content-Disposition': 'inline; filename="pixel.gif"' };
    return picture;
}
module.exports = main;
```

## Calling API Maker : `g.sys`

**Database calls**

```typescript
import * as T from 'types';

async function main(g: T.IAMGlobal) {
    const table = { instance: 'mysql_8', database: 'inventory', collection: 'customers' };
    const rows = await g.sys.db.getAll({ ...table, queryParams: { limit: 10, sort: '-customer_id' } });
    const two = await g.sys.db.getById({ ...table, id: 2 });
    const count = await g.sys.db.count({ ...table, find: { country: 'IN' } });
    return { first: rows[0], two, inIndia: count };
}
module.exports = main;
```

**Calls at the same time**

```typescript
import * as T from 'types';

async function main(g: T.IAMGlobal) {
    const shop = { instance: 'mongodb', database: 'shop' };
    const [orders, products] = await Promise.all([
        g.sys.db.count({ ...shop, collection: 'orders' }),
        g.sys.db.count({ ...shop, collection: 'products' }),
    ]);
    return { orders, products };
}
module.exports = main;
```

**Rows as they arrive**

```typescript
import * as T from 'types';

async function main(g: T.IAMGlobal) {
    let amount = 0;
    await g.sys.db.getAllByStream({ instance: 'mysql_8', database: 'inventory', collection: 'orders' }, (row: any) => {
        amount += row.amount || 0;
    });
    return { amount };
}
module.exports = main;
```

Every call of `g.sys` has its own page of examples : [database](https://docs.apimaker.dev/v4/examples/sys/db/getAll.html), [generated APIs](https://docs.apimaker.dev/v4/examples/sys/db/getAll.html#without-a-schema), [system APIs](https://docs.apimaker.dev/v4/examples/sys/system/system.html), [cache](https://docs.apimaker.dev/v4/docs/apis-all/system-apis/system-generated-set-redis-key-api.html#from-code).

## Errors

Throw a list of errors to answer with them :

**Refuse a request**

```typescript
import * as T from 'types';

async function main(g: T.IAMGlobal) {
    if (!g.req.body.quantity) {
        throw [{ code: T.EStatusCode.BAD_REQUEST, message: `'quantity' is missing.` }];
    }
    return { ok: true };
}
module.exports = main;
```

**Catch the errors of a call**

```typescript
import * as T from 'types';

async function main(g: T.IAMGlobal) {
    try {
        await g.sys.db.removeById({ instance: 'mysql_8', database: 'inventory', collection: 'customers', id: 404 });
        return { removed: true };
    } catch (errors) {
        g.logger.warn('not removed : ', errors[0].message);
        return { removed: false, code: errors[0].code };
    }
}
module.exports = main;
```

An exception answers `500` with its message and the lines of your TypeScript (the source map of the save is applied).

## Logs, printing and shared values

**Logs and shared values**

```typescript
import * as T from 'types';

async function main(g: T.IAMGlobal) {
    g.logger.log('order ', 7, ' paid');
    console.log('kept in the console of the account');
    g.shared.calls = (g.shared.calls || 0) + 1;   // lives as long as the runner
    g.res.shared.started = true;                  // travels with this request : pre hook → API → post hook
    return g.shared.calls;
}
module.exports = main;
```

## Hooks

**Pre hook : only my rows**

```typescript
import * as T from 'types';

async function main(g: T.IAMGlobal) {
    const person = g.req.auth?.authAMDB || {};
    g.req.query.find = { ...(g.req.query.find || {}), owner_id: person.id };
}
module.exports = main;
```

**Post hook : hide a column**

```typescript
import * as T from 'types';

async function main(g: T.IAMGlobal) {
    g.res.output = (g.res.output || []).map(({ password, ...row }: any) => row);
}
module.exports = main;
```

## Packages

Add the package in [Sandbox Settings](https://docs.apimaker.dev/v4/docs/settings/sandboxSettings.html#packages) (`dayjs` `1.11.13`), then import it :

**An npm package**

```typescript
import * as T from 'types';
import * as dayjs from 'dayjs';

async function main(g: T.IAMGlobal) {
    return { tomorrow: dayjs().add(1, 'day').format('YYYY-MM-DD') };
}
module.exports = main;
```

- Versions are what npm takes : `4.17.21`, `^4.17.0`, `latest`.
- Nothing is installed unless you add it : a new sandbox has only the packages of its runtime.
- Types of the packages are read by the code editors from the sandbox.

## Utility classes

Code in TypeScript imports the [utility classes](https://docs.apimaker.dev/v4/docs/utility-class/utility-class.html) written in TypeScript on both runtimes (`import { Tax } from 'utils/Tax';`), and they import each other and your packages. A class written in [another language](https://docs.apimaker.dev/v4/docs/languages/index.html#utility-classes) is for the code of that language.

## How it runs

- The code is compiled to JavaScript when you save it; the runner keeps the compiled function by the hash of the code.
- One runner serves many requests at once on its event loop; the sandbox opens more runners while all are busy.
- Values keep their kind inside one sandbox (dates are `Date`, ids are `ObjectId`); a request which comes from outside reaches the code as JSON.

## Debugging

- Turn debugging on in the debug settings, set a breakpoint in the margin of the editor and send the request : the code stops on the line of your TypeScript, on Node.js and on Bun alike (its source map is applied for you), and the **Debug** tab of the API testing page shows its variables and its console.
- Learn more [Debugging](https://docs.apimaker.dev/v4/docs/features/debugging.html).

## Related

- [Languages and runtimes](https://docs.apimaker.dev/v4/docs/languages/index.html) · [The global object g](https://docs.apimaker.dev/v4/docs/pre-defined-terms/global-object-g.html) · [Code examples](https://docs.apimaker.dev/v4/examples/index.html) · [Sandbox settings](https://docs.apimaker.dev/v4/docs/settings/sandboxSettings.html)
