The batteries a small app needs: auth, data, storage, cron, email
A small internal app needs auth, a database, file storage, cron, and email before it does anything useful. Standing each up yourself is the tax that kills small software.
Here is the dirty secret of small software: the app is the easy part. An expense approval flow is two tables and four functions. A signature request is a form and an email. The reason these never get built is not the fifty lines of logic. It is everything that has to exist before those fifty lines do anything useful.
Call it the setup tax. Before your app can approve a single expense, someone has to sign in, the data has to persist, a file has to upload, a reminder has to fire, and an email has to send. Each of those is a service you provision, a config you babysit, a secret you store somewhere. Do it once and it is a week. Do it for every little tool a team wants and you stop building tools.
A batteries-included cloud pays that tax for you.
On fuast you import fuast/server and the batteries are just there. Here is each
one and why standing it up yourself is the thing that actually kills the app.
Auth: verified sign-in you did not build
Every internal tool needs to know who is using it. Not "a username in a text box" know, but verified know, so finance can approve and sales cannot approve on their behalf. Building that means an identity provider, session handling, and a login page, all before your app does anything.
On fuast, identity is handed to your handler. ctx.auth is { userId, isGuest, email, displayName }, already verified by the platform. You never accept a user id
as an argument, you read it off the context and trust it. Sign-in is the platform's
job; you filter rows by ctx.auth.userId and move on.
Database: a table and a query, no migrations
The tool has to remember things. The normal path is provision a database, manage a connection, write migrations, keep it patched. For a two-table app that is absurd, so people reach for a spreadsheet and regret it later.
A fuast capsule declares its schema inline and gets a real database:
import { capsule, table, string, mutation, query } from "fuast/server";
export default capsule({
schema: {
expenses: table({ memo: string(), amount: string(), status: string() })
.index("by_status", ["status"]),
},
queries: {
pending: query(async (ctx) =>
ctx.db.expenses.withIndex("by_status", (q) => q.eq("status", "pending")).collect(),
),
},
mutations: {
submit: mutation(async (ctx, memo: string, amount: string) => {
if (ctx.auth.isGuest) throw new Error("sign in first");
return ctx.db.expenses.insert({ memo, amount, status: "pending" });
}),
},
});
That is a working backend. ctx.db.<table> gives you insert, get, update,
delete, indexed reads, and ordering. No migration file, no connection string.
File storage: uploads that just work
The moment a tool touches receipts or signed PDFs, you need storage, access control, and serving URLs. Rolling that yourself is a bucket, a policy, and a signing scheme.
ctx.storage covers it: url(key) gives an access-controlled serving URL,
get(key) and delete(key) do the rest. On the client, uploadFile(file) returns
a key you save with a mutation. The access wall is the platform's, not yours.
Cron: a function on a clock
Half of these tools need something to happen on a schedule. A daily digest of pending approvals. A reminder that a signature is still open. Normally that is a separate cron service you deploy and monitor.
A schedule is one line in the same file:
import { capsule, schedule } from "fuast/server";
export default capsule({
schema: { /* ... */ },
schedules: {
dailyDigest: schedule("0 9 * * *", async (ctx) => { // 09:00 UTC
const rows = await ctx.db.expenses.collect();
await ctx.email.send({ to: "finance@you.com", subject: "Pending", text: `${rows.length} open` });
}),
},
});
Standard five-field cron, UTC, resynced on every deploy. No second service.
Email and secrets: the last two batteries
Transactional email is its own project: a provider account, an API key, a sender
domain. On fuast it is ctx.email.send({ to, subject, text }). Console in dev,
real delivery in prod once you set the provider key. And that key lives in
ctx.env, the built-in secrets store, write-only from the dashboard and never
exposed to the client. No vault to run.
The point of included batteries
Every battery above is a real service someone normally provisions, secures, and maintains. Stacked up, they are the reason the fifty-line app costs a week and therefore never ships. A small cloud is the bet that this whole layer should be assembled once, by the platform, so you write the app and nothing else.
That is what small software needs to exist: a small cloud where the plumbing is already there. Tell your AI what the tool should do, and the batteries are waiting for the code it writes. Build one this afternoon.
Build one this afternoon.
Tell your AI what your team needs. fuast runs it and signs everyone in.
Start free