Your first app
Reports UI
The final tab, Reports, introduces a new workload type: a task — background code that runs independently of the web server.
The Reports tab will have a button that triggers a background task to generate a report, upload it, and make it appear in the Documents tab.
Generate a task workload
Let's start by defining a task workload, monolayer's abstraction for asynchronous background tasks
npx monolayer add task --name upload-reportUpdate task code
Now, open workloads/generate-report.task.ts and replace the contents with:
import { PutObjectCommand } from "@aws-sdk/client-s3";
import { Task } from "@monolayer/sdk";
import documents from "./documents";
import { publisher } from "@/lib/broadcast/publisher";
import { s3Client } from "@/lib/bucket/client";
export type UploadReportData = {
message: string;
report: { message: string };
};
const uploadReport = new Task<UploadReportData>(
"upload-report",
async ({ data }) => {
console.log("message", data.message);
const key = `Report-${new Date().toISOString()}`;
const command = new PutObjectCommand({
Bucket: documents.name,
Key: key,
Body: Buffer.from(data.report.message),
});
await s3Client.send(command);
},
);
export default uploadReport;Add components and actions
"use server";
import uploadReport from "@/workloads/upload-report";
export async function generateReport() {
await uploadReport.performLater({ report: { message: "hello" } });
return true;
}"use client";
import { generateReport } from "@/actions/generate-report";
import { Button } from "./ui/button";
export function GenerateReport() {
return (
<Button
variant="default"
className="hover:cursor-pointer"
onClick={generateReport}
>
Generate Report
</Button>
);
}Show Generate Report in home
Finally, connect your report button to the Reports tab in your app's main page.
import { Documents } from "@/components/documents";
import DocumentsProvider from "@/components/documents/provider";
import { Upload } from "@/components/documents/upload";
import { GenerateReport } from "@/components/generate-report";
import { AddTodo } from "@/components/todo-list/add";
import TodosProvider from "@/components/todo-list/provider";
import { Todos } from "@/components/todo-list/todos";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { allDocuments } from "@/lib/documents";
import { allTodos } from "@/lib/todos";
export default function Home() {
return (
<TodosProvider todos={allTodos()}>
<DocumentsProvider items={allDocuments()}>
<main className="min-h-screen dark text-primary bg-slate-950 p-6">
<div className="flex flex-col gap-10">
<h1 className="text-2xl font-bold text-center">
monolayer Starter
</h1>
<Tabs defaultValue="todos" className="items-center w-full">
<TabsList className="w-2xs">
<TabsTrigger value="todos">Todos</TabsTrigger>
<TabsTrigger value="documents">Documents</TabsTrigger>
<TabsTrigger value="reports">Reports</TabsTrigger>
</TabsList>
<TabsContent value="todos" className="w-full align-start">
<div className="py-4 max-w-2xl mx-auto">
<AddTodo />
<Todos />
</div>
</TabsContent>
<TabsContent value="documents" className="w-full align-start">
<div className="py-4 max-w-2xl mx-auto">
<Upload />
<Documents />
</div>
</TabsContent>
<TabsContent value="reports" className="w-full align-start">
<div className="py-4 max-w-2xl mx-auto">
<GenerateReport />
</div>
</TabsContent>
</Tabs>
</div>
</main>
</DocumentsProvider>
</TodosProvider>
);
}Now, open your app and click Generate Report.

After a few seconds, refresh the Documents tab — you'll see a new file listed, automatically uploaded by the background task.
