Data where it lives
Loaders read your tables next to the database. A website reaches only the tables it lists, and each visitor only the rows its rule gives: { customer_id: '$user._id' }.
API Maker
The backend engine for the AI era
Your code
Websites written in TSX, rendered by the server next to your data. Secure by default, fast without trying.
Make a website from a template, write its pages in TSX in the admin panel, and see them in a live preview. The server renders each page next to your database and sends HTML: sign-in with your auth providers, forms with CSRF tokens, the rows each visitor may reach, a page cache, UI libraries and Git, all built in.
Starter, Blog, Shop, Dashboard, Landing page or Documentation: a whole website full of commented examples.
A file of pages/ is a route. Its loader reads your tables on the server, its component returns HTML.
Each file is compiled when you save it; the preview reloads by itself and shows where an error comes from.
It answers at /ssr/<apiPath>/<name> at once. A reverse proxy shows it at the root of its own domain.
Loaders read your tables next to the database. A website reaches only the tables it lists, and each visitor only the rows its rule gives: { customer_id: '$user._id' }.
Visitors sign in with the auth providers of your account, and pages say who may open them. Every value is escaped, with a CSP with nonces, CSRF tokens on every form and sealed session cookies.
The server keeps whole pages, dropped by tags. Titles, Open Graph, JSON-LD and sitemap.xml are built in, and Bootstrap, Tailwind, Chart.js, htmx, Alpine and 35 more libraries are one click away.
The Shop template lists products with filters, keeps the cart in a cookie, signs customers in with the auth provider of your customers table, and saves orders which each customer alone can read. Put shop.example.com in front of it with nginx, and it is live.
import { formatCurrency } from 'am-ssr';import type { PageConfig, PageContext, PageProps } from 'am-ssr';export const config: PageConfig = { title: 'Products', cache: { maxAge: 60, scope: 'public' } };export async function loader(ctx: PageContext) { return { products: await ctx.db.query({ instance: 'mongodb', database: 'shop', table: 'products', limit: 20 }) };}export default function Products({ data }: PageProps<typeof loader>) { return ( <ul> {data.products.map((product) => ( <li><a href={`/products/${product.slug}`}>{product.name}</a> {formatCurrency(product.price, 'USD')}</li> ))} </ul> );}Every value is escaped, and /products gets the address of the website in front of it by itself.
import { defineSite } from 'am-ssr';export default defineSite({ title: 'My shop', auth: { providers: ['customers'], loginPath: '/login' }, data: { read: ['mongodb/shop/products', { table: 'mongodb/shop/orders', where: { customer_id: '$user._id' } }], write: [{ table: 'mongodb/shop/orders', where: { customer_id: '$user._id' }, allow: ['create'] }], },});A customer reads and creates its own orders, nobody else's, whatever a page asks.
No. Pages are TSX, which reads like HTML with values in braces, rendered on the server. Nothing runs in the browser unless you add a script or a UI library.
A website reaches only the tables its settings list, and a rule keeps each visitor to its own rows. Pages which ask for a signed-in visitor or a group are refused before any of their code runs.
Yes. The templates are commented for it, and the docs have a guide written for assistants, with the rules and complete examples.
Documentation