CAKE20

AI Web PaaS

Technology specific implementation examples

Check the functions provided by Cake20 through short source code and actual execution results.

Cake20 View pages and components

// app/components/GreetingCard.tsx
export type Props = { name: string };

export default ({ name }: Props) => <Card>Hello, {name}</Card>;

// app/pages/index.tsx
export default () => <GreetingCard name="cafe" />;

Result

A ‘Hello, Cafe’ card will appear on the home screen. Component import is not required.

Cake20 UI and Tailwind CSS

export default () => (
  <Button icon="i-lucide-save" class="mt-4" onClick={save}>save</Button>
);

Result

You will see a Cake20 UI button with a Lucide save icon. No need for U prefix and package installation.

WYSIWYG Editor

export const data = {
  content: "<h2>Today’s record</h2><p>Write down the contents.</p>",
};

export default () => (
  <Editor bind={data.content} image={false} mention={false} />
);

Result

You'll see a Tiptap-based formatting editor that syncs bi-directionally with your HTML strings.

DragList drag sorting

export default () => (
  <DragList bind={data.cards} group="tasks" onEnd={saveOrder}>
    {data.cards.map((card) => <Card key={card.id}>{card.title}</Card>)}
  </DragList>
);

Result

You can grab all the cards and change their order or move them to another list in the same group.

Website visibility settings

// package.json
{
  "title": "Neighborhood cafe",
  "description": "Introducing today’s menu.",
  "timezone": "Asia/Seoul",
  "lang": "en"
}

Result

Document title and public meta information are applied, and server time calculation uses the Seoul standard.

Responsive Store

// app/stores/cart.store.ts
export const config = { persist: "local" } as const;
export default {
  count: 0,
  add() { this.count += 1; }
};

// app/pages/index.tsx
export default () => (
  <Button onClick={() => store.cart.add()}>{store.cart.count}</Button>
);

Result

When you press the button, the number increases and is recovered from localStorage even when you reopen the browser.

Provide Context to descendants

// app/components/ThemePanel.tsx
export const data = { theme: "dark" as "light" | "dark" };
export const context = { theme: () => data.theme };
export default () => <ThemeLabel />;

// app/components/ThemeLabel.tsx
const theme = useContext<"light" | "dark">("theme");
export default () => <p>Current theme: {theme()}</p>;

Result

A descendant uses the nearest parent context value without prop forwarding. Use context for UI subtree state and a store for global or persistent state.

Shared automatic import and runtime functions

// shared/utils/money.ts
export const money = (value: number) => value.toLocaleString("ko-KR") + "one";

// app/pages/index.tsx or server/api/price.get.ts
const data = await fetchGet<{ price: number }>("/api/price");
const label = money(data.price);
const day = textDate(now());

Result

Common functions and fetchGet·now·textDate are used together in the UI and server without separate import.

API and input validation

// server/api/memos.post.ts
export const config = {
  filter: { title: z.string().trim().min(1).max(80) },
  sample: { title: "first note" }
};
export default async (data: Input<typeof config.filter>) =>
  db.memo.create({ data });

Result

A POST /api/memos is generated and invalid input is blocked with a 400 response before DB execution.

Inquiry form storage box

// server/api/contact.post.ts
export default defineApi(async (input, { request }) =>
  form.submit(request, {
    name: "contact",
    consent: input.consent === true,
    data: { email: input.email, message: input.message }
  })
);

Result

Inquiries are stored in a dedicated website archive, and speed limit, capacity limit, and consent information are applied together.

Prisma database model

// server/db/memo.db.ts
export const Memo = {
  id: z.id(),
  title: z.string().max(80),
  createdAt: z.date().defaultNow().timestamp()
};

Result

The Memo table and Prisma access object db.memo are created. Use one db.ts file per table.

Initial data and preview samples

// server/db/memo.db.ts
export const Memo = {
  id: z.string().id().max(30),
  title: z.string().max(100)
};

export const seed = async () => [
  { id: "first", title: "First note" },
  { id: "shopping", title: "Grocery shopping" }
];

Result

Cake20 builds Preview JSON from the current rows and upserts only new or changed seeds.

Reusable SQL functions

// server/db/memo.sql.ts
export const recentMemos = db.sql(
  z.object({ id: z.string(), title: z.string() }),
  () => sql`SELECT id, title FROM "Memo" ORDER BY "createdAt" DESC LIMIT 5`
);

const rows = await db.sql.recentMemos();

Result

An array of verified recent notes is returned, and the same function can be reused in API·Hook·Task.

Login and permissions

// server/api/me.get.ts
export default async (_data: unknown, context: ApiContext) => {
  const user = await auth.require(context.request, { roles: ["admin"] });
  return { email: user.email };
};

Result

Administrators receive an email, non-logged users receive a 401 response, and unauthorized users receive a 403 response.

Routes, WebSockets and SSE

// server/routes/notice.sse.ts
export default () => ({
  event: "notice",
  data: { text: "Your order is ready." }
});

// server/routes/chat.ws.ts
export default { onMessage(peer, message) { peer.publish("room", message); } };

Result

The browser receives server notifications in /notice and real-time messages from the same room in /chat.

Hook, Task, Job and Schedule

// server/hooks/order.hook.ts
export default (order: Order) => console.log(order.id);

// server/tasks/cleanup.task.ts
export default async () => ({ removed: await db.memo.deleteMany() });

// server/tasks/daily.job.ts
export const config = { cron: "0 0 * * *" };
export default async () => db.report.create({ data: { day: textDate(now()) } });

Result

Event hooks, manual tasks, and daily midnight jobs are managed with independent files and execution history.

Redis-based asynchronous Job

// server/api/report.post.ts
const work = await job.report({ month: "2026-08" });
return { id: work.id };

// status inquiry
const state = await job.$get(work.id);

Result

The API immediately returns the job ID, and the server job manages retries and status in the website-specific Redis Queue.

Alarms, emails and payments

await mail.send({ to: "[email protected]", subject: "Sign up completed", text: "welcome." });

if (telegram.active) await telegram.send("A new order has been registered.");

if (gmail.active) {
  await gmail.send({ subject: "new order", text: "A new order has been registered." });
}

const ready = await payment.request({
  orderId: "ORDER-1",
  orderName: "strawberry cake",
  amount: 12000,
  returnUrl: "https://example.com/pay/success",
  cancelUrl: "https://example.com/pay/fail"
});

Result

Send notifications to site administrator Telegram·Gmail and SMTP and return payment request information.

Files and Object Storage

const file = await storage.put("images/menu.webp", body);
return { url: storage.url(file.path) };

Result

A publishable image URL is returned regardless of local and Object Storage settings.

Create Excel XLSX

const rows = [["Order"], ["Strawberry cake"]];
return excel.download("orders.xlsx", rows);

Result

Simple rows are returned as an XLSX download Response without persisting them to storage.

executable test

test("Note list", async () => {
  const data = await fetchGet<{ items: unknown[] }>("/api/memos");
  expect(data.items.length).toBeGreaterThan(0);
});

Result

The inspection build actually calls the API, and the pass/fail results are displayed in the editor test console.