# Custom API Example

> Learn how to create and implement custom APIs in API Maker with this comprehensive example, including code snippets and best practices.

Source: https://docs.apimaker.dev/v1/examples/custom-apis/custom-api.html

<!-- --8<-- [start:body] -->


The code of a [custom API](/v1/docs/apis-all/custom-apis/user-created-custom-api.html) : what to return, how to call the APIs of API Maker, how to send files.

## Basic code

- A custom API is an `async function main(g)` : what it returns is the `data` of the answer.

```ts linenums="1"
return {
    hello: 'world'
};
```

## await

- Every `g.sys` call returns a promise : `await` it. A call without `await` runs, but its result and its errors are lost.

```ts linenums="1"
return await g.sys.db.getAll({
    instance: "INSTANCE_NAME", database: 'DB_NAME', collection: "COLLECTION_NAME",
});
```

## The second argument : `true`

- Without it, a `g.sys` call returns the data and **throws** the errors of the API.
- With `true`, it returns the whole envelope `{ success, statusCode, data, errors, warnings }` and never throws : read `success` yourself.

```ts linenums="1"
let saveData = await g.sys.db.saveSingleOrMultiple({
    instance: "INSTANCE_NAME", database: 'DB_NAME', collection: "COLLECTION_NAME",
    saveData: {},
    headers: {}
}, true);
let countOfAllData = await g.sys.db.count({
    instance: "INSTANCE_NAME", database: 'DB_NAME', collection: "COLLECTION_NAME",
}, true);
return { saveData, countOfAllData };
```

## Throw an error

- `throw` ends the call with `success: false` and the message in `errors`. A message listed in the `errorList` of the API can be [translated](/v1/docs/i18/i18.html).

```ts linenums="1"
let saveData = await g.sys.db.saveSingleOrMultiple({
    instance: "INSTANCE_NAME", database: 'DB_NAME', collection: "COLLECTION_NAME",
    saveData: {},
    headers: {}
}, true);
if (saveData.success === false) throw 'Your data is not saved!';
else return saveData;
```

## Return an image or any binary

- Set `g.res.contentType` and return the base64 of the content.

```ts linenums="1"
g.res.contentType = 'image/gif';
return `data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOwA=`
```

## Download file

```ts linenums="1"
import * as T from 'types';
import * as fs from 'fs';
import * as path from 'path';
async function main(g: T.IAMGlobal) {
    // always write file in uploads directlry which you want to send to user.
    // you can write other files in any directory which you don't want to send to user.
    let filePath = path.join(__dirname, 'uploads', 'myfile.txt');
    await fs.promises.writeFile(filePath, 'file 1 content', { encoding: 'utf8' });
    return {
        // just provide file name which is written to "uploads" directory
        __am__downloadFilePath: 'myfile.txt',
        __am__downloadFolderFileName: 'newName.txt', // download file will have this name.
    }
};
module.exports = main;
```

```ts linenums="1"
import * as T from 'types';
import * as fs from 'fs';
import * as path from 'path';
async function main(g: T.IAMGlobal) {
    return {
        __am__downloadFileOrFolderPaths: [
            {
                fsSource: path.join('folder1', 'folder2', 'folder3', 'data_folder'), // path into uploads folder
                archiveDestination: 'data_folder', // path in zip file, so data_folder folder will be directly available in zip file.
            },
        ],
        __am__downloadFolderFileName: `output.zip`, // download file will have this name.
    }
};
module.exports = main;
```

## Download files & folders

```ts linenums="1"
import * as T from 'types';
import * as fs from 'fs';
import * as path from 'path';
async function main(g: T.IAMGlobal) {
    // always write file in uploads directlry which you want to send to user.
    // you can write other files in any directory which you don't want to send to user.
    let filePath = path.join(__dirname, 'uploads', 'myfile.txt');
    await fs.promises.writeFile(filePath, 'file 1 content', { encoding: 'utf8' });
    return {
        // You can provide list of files or folders, it will will make zip and send to user.
        __am__downloadFileOrFolderPaths: [
            'myfile.txt', // file name
            'someFolder', // folder name
        ],
        __am__downloadFolderFileName: 'zipFile.zip', // this will be zip file name
    }
};
module.exports = main;
```

## Download file from Angular sent by custom API

```ts linenums="1"
async function downloadFileFromBrowser() {
    const requestPayload = {};
    const requestHeaders = {};
    const resp = <any>await this.http.post(`API Maker custom API endpoint`, requestPayload, {
        headers: requestHeaders,
        responseType: <any>'text',
        observe: 'response',
    }).toPromise();
    const fileName = resp.headers.get('content-filename');
    await this.downloadFileFromBlob('application/zip', resp.body, fileName);
    return resp;
}

async function downloadFileFromBlob(contentType, base64Data: any, fileName) {
    const a = document.createElement('a');
    document.body.appendChild(a);
    a.style.display = 'none';

    let blob;
    if (base64Data instanceof Blob) {
        blob = base64Data;
    } else {
        const base64Response = await fetch(`data:${contentType};base64,` + base64Data);
        blob = await base64Response.blob();
    }

    const url = window.URL.createObjectURL(blob);
    a.href = url;
    a.download = fileName;
    a.click();
    window.URL.revokeObjectURL(url);
}
```

## Multi-tenant

- Call a custom API with the [x-am-tenant-username](/v1/docs/apis-all/header/requestHeader.html#x-am-tenant-username) header to run it for one tenant of a [multi-tenant](/v1/docs/features/multi-tenant.html) instance.
- It reads the tenant in `g.req.params`, and the APIs it calls with `g.sys.db` and `g.sys.system` work on the database of that tenant: the header is passed on, and each instance finds the tenant in its own tenants table.
- `instance: 'crm::globex'` names another tenant for one call, and an empty tenant header names the structure database. See [Custom APIs of a multi-tenant instance](/v1/docs/features/multi-tenant.html#custom-apis).

```text linenums="1"
GET /api/custom-api/admin/customers-report
x-am-tenant-username: acme
```

```ts linenums="1"
import * as T from 'types';

async function main(g: T.IAMGlobal) {
    const tenant = g.req.params.tenantUsername; // 'acme'
    const customers = await g.sys.db.getAll({ instance: 'crm', database: 'crm', collection: 'customers' }); // from the database of acme
    const customersOfGlobex = await g.sys.db.count({ instance: 'crm::globex', database: 'crm', collection: 'customers' });
    return { tenant, customers, customersOfGlobex };
}
module.exports = main;
```

<!-- --8<-- [end:body] -->
