Browsers in custom APIs¶
A custom API can drive a headless Chromium with Playwright or Puppeteer : turn HTML into a PDF, take a screenshot, or read a page. A browser needs system libraries next to its npm package, so it runs in one of two places.
| Native process | Sandbox | |
|---|---|---|
| Where the code runs | Inside API Maker itself (runOnNativeProcess: true) |
In the sandbox containers of the account |
| Where the browser is installed | On the server, next to API Maker | In the Docker file of the sandbox |
| Isolation | None : a crash of the browser can hurt the server | Your code stays apart from API Maker |
| Set up by | The root user of the server | An admin, from the admin panel |
A browser needs memory : plan 2 GB of RAM and 40 GB of disk for the server, and avoid many browsers at the same time.
Playwright on the native process¶
-
Sign in to the server as root (
whoamiprintsroot;sudo -sotherwise) and install Chromium with the libraries it needs : -
Add the package to API Maker :
-
Create a custom API and set
runOnNativeProcess: truein its configuration (the Basic Info section of the custom API). -
Use it in the code. This one writes a PDF from HTML into the
uploadsfolder :
import * as T from 'types';
import { join } from 'path';
const playwright = require('playwright');
async function main(g: T.IAMGlobal) {
const browser = await playwright.chromium.launch();
const page = await browser.newPage();
await page.setContent('<p style="font: 18px Arial">Hello from Playwright</p>', { waitUntil: 'load' });
await page.pdf({
path: join(__dirname, 'uploads', 'playwright.pdf'),
format: 'A4',
printBackground: true,
margin: { top: '20mm', right: '20mm', bottom: '20mm', left: '20mm' },
});
await browser.close();
// uploads/playwright.pdf is on the server : upload it to a storage provider or send it as a download.
return 'pdf created on the server';
}
module.exports = main;
Every server, every upgrade
A package installed by hand lives on one server. Put the install command in Root Settings → Deployment Settings → Scripts → Server Startup Script : the script runs when the API Maker process starts, on every server of the cluster, so the package is there on new servers too.
Playwright in the sandbox¶
The default image of the sandbox has no browser libraries. Two changes in the sandbox settings add them.
-
Docker file : replace the content with the file below and click Save (top right). It installs Chromium, its system libraries and pnpm.
FROM node:22-bookworm WORKDIR /usr/src/app RUN npm install -g [email protected] RUN apt-get update && apt-get install -y --no-install-recommends build-essential g++ make libc6 python3 python3-dev && rm -rf /var/lib/apt/lists/* ARG A_DOCKERFILE_HASH ENV A_DOCKERFILE_HASH=${A_DOCKERFILE_HASH} ARG A_PACKAGE_JSON_HASH ENV A_PACKAGE_JSON_HASH=${A_PACKAGE_JSON_HASH} COPY ./package.json ./ RUN pnpm install --dangerously-allow-all-builds RUN npx -y [email protected] install --with-deps ARG A_CODE_HASH ENV A_CODE_HASH=${A_CODE_HASH} ARG NODE_OPTIONS ENV NODE_OPTIONS=${NODE_OPTIONS} COPY . . ARG A_NEW_PACKAGES_INSTALL_CMD ENV A_NEW_PACKAGES_INSTALL_CMD=${A_NEW_PACKAGES_INSTALL_CMD} RUN $A_NEW_PACKAGES_INSTALL_CMD EXPOSE 4631 EXPOSE 4632 EXPOSE 9229 CMD [ "npm", "run", "start" ] -
Dependencies : click Add (+), search for
playwright, pick the latest stable version and click Add.
Launch Chromium with --no-sandbox inside the container. This custom API saves a web page as a PDF and sends it as a download :
import * as T from 'types';
const fs = require('fs');
const path = require('path');
const { chromium } = require('playwright');
async function main(g: T.IAMGlobal) {
const outDir = path.join(__dirname, 'uploads');
if (!fs.existsSync(outDir)) fs.mkdirSync(outDir, { recursive: true });
const browser = await chromium.launch({ headless: true, args: ['--no-sandbox', '--disable-setuid-sandbox'] });
const page = await browser.newPage();
await page.goto('https://playwright.dev', { waitUntil: 'networkidle' });
await page.pdf({ path: path.join(outDir, 'playwright-dev.pdf'), format: 'A4' });
await browser.close();
return {
__am__downloadFilePath: 'playwright-dev.pdf',
__am__downloadFolderFileName: 'playwright-dev.pdf',
};
}
module.exports = main;
Puppeteer on the native process¶
-
Sign in to the server as root and install the libraries of Chromium (Ubuntu ; for other systems see the troubleshooting page of Puppeteer) :
apt update && apt install -y ca-certificates fonts-liberation libatk1.0-0t64 libatk-bridge2.0-0t64 \ libc6 libcairo2 libcups2 libdbus-1-3 libexpat1 libfontconfig1 libgbm1 libgcc1 libglib2.0-0 \ libgtk-3-0 libnspr4 libnss3 libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 \ libxcb1 libxcomposite1 libxcursor1 libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 \ libxss1 libxtst6 libasound2t64 lsb-release wget xdg-utils -
Add the package to API Maker :
-
Set
runOnNativeProcess: truein the configuration of the custom API, and use it :
import * as T from 'types';
import { join } from 'path';
const puppeteer = require('puppeteer');
async function main(g: T.IAMGlobal) {
const browser = await puppeteer.launch({ headless: 'new', args: ['--no-sandbox', '--disable-setuid-sandbox'] });
const page = await browser.newPage();
await page.setContent('<p>Hello, this paragraph is converted to a PDF using Puppeteer.</p>', { waitUntil: 'load' });
await page.pdf({ path: join(__dirname, 'uploads', 'puppeteer.pdf'), format: 'A4', printBackground: true });
await browser.close();
// uploads/puppeteer.pdf is on the server : upload it to a storage provider or send it as a download.
return 'ok';
}
module.exports = main;
Good to know¶
- Write the files you want to send into the
uploadsfolder and return__am__downloadFilePath: see Download file. Upload them to a storage service instead when they must be kept. - Close the browser in every path of the code, errors included : an open browser keeps its memory.
- A bug on the native process can hurt the whole server : prefer the sandbox when you can, and keep the native process for the APIs which need it.