Skip to content

From idea to production-ready SaaS.

Workspaces, auth, APIs, and observability, built on Cloudflare. Structured logs and request tracing are included; external monitoring is optional.

Follow a request through the code.

Start with the workspace overview. Inspect its API contract and shared function, then the infrastructure behind the app.

View the architecture
Request topology of the B2B SaaS StarterbrowserHTTP clientMCP clientqueue jobswebTanStack StartapiREST + MCPbackgroundqueue consumerpackages/capabilitiesD1Durable ObjectsQueuesEmail

See what the API returns.

The workspace overview supplies the data on this page.

REST · GET /workspaces/:slug/overview
curl -H "Authorization: Bearer $API_TOKEN" \
  https://api.example.com/workspaces/starter-lab/overview

{
  "workspace": {
    "id": "wrk_starter",
    "slug": "starter-lab",
    "name": "Starter Lab",
    "planId": "team"
  },
  "notifications": []
}

This deployment has no sample workspace. The hostname and response show an example using seed data.

Check the inputs and errors.

Each route declares its parameters, response, and failures. The group requires bearer authentication.

HttpApiEndpoint · the workspace grouppackages/api/src/index.ts (opens in new tab)
export const WorkspaceApi = HttpApiGroup.make('workspace')
  .add(
    HttpApiEndpoint.get('overview', '/workspaces/:slug/overview', {
      params: SlugParams,
      success: WorkspaceOverviewDto,
      error: WORKSPACE_ERRORS
    })
  )
  .middleware(BearerAuth)

Use the same function everywhere.

The web app, REST, and MCP call the same capability.

Effect · the overview projectionpackages/capabilities/src/workspace-projections.ts (opens in new tab)
export const workspaceOverview: Effect.Effect<
  WorkspaceOverviewProjection,
  CapabilityUnavailable,
  WorkspaceContext | NotificationFeed
> = Effect.gen(function* () {
  const ctx = yield* WorkspaceContext
  const feed = yield* NotificationFeed
  const notifications = yield* feed.list
  return {
    workspace: ctx.workspace,
    notifications
  }
})
Compare the web, REST, and MCP callers
server fnapps/web/src/lib/server/demo-showcase.effects.ts (opens in new tab)
return runWorkspaceCapabilities(
  DEMO_WORKSPACE_SLUG,
  Effect.all(
    {
      overview: workspaceOverview,
      …
    },
    { concurrency: 'unbounded' }
  )
)
REST handlerapps/api/src/handlers.ts (opens in new tab)
.handle('overview', ({ params, request }) =>
  workspaceRead(READ_OPERATIONS.overview, params, undefined, request)
)
MCP toolapps/api/src/mcp.ts (opens in new tab)
const caller = yield* requireCaller()
const invoke = yield* decodeOperationInput(operation, payload)
…
const guarded = Effect.gen(function* () {
  yield* authorizeMcpOperation(caller, operation.permission, MCP_READ_SCOPE)
  return yield* invoke
}).pipe(Effect.scoped)

Deploy to Cloudflare.

Cloudflare Workers run the app and API. D1 stores business records, Durable Objects persist private assistant conversations, and Queues and Email handle delivery.

Workers

Web app, REST and MCP API, and background jobs

apps/web · apps/api · apps/background

D1

Workspace data, schema, and migrations

packages/db

Durable Objects

Private assistant history, one active answer and reconnect replay

apps/web · WorkspaceAssistantConversation

Queues

Webhook delivery with retries and backoff

apps/background

Email

Transactional email, enabled when configured

packages/email

pnpm run deploy

Add providers when you need them.

These integrations stay off until you configure them. You can run the app without them.

Add providers when you need them.
ProviderRole
StripeBilling
Cloudflare Web AnalyticsWebsite analytics
Cloudflare EmailTransactional email
TurnstileBot protection

Read how it works.

The setup guide and architecture docs live alongside the code.

docs/

Before you start

Setup, architecture, deployment, and licensing.

The web app runs on a Cloudflare Worker. TanStack Start provides file-based routing, typed loaders, and server functions that call the same capabilities as the REST API.

Run it on your machine.

Clone the repo and run the commands below. The sample workspace runs locally without a Stripe account, OAuth app, or email provider.

Clone and run

$ git clone
https://github.com/brandhaug/b2b-saas-starter.git
$ cd
b2b-saas-starter
$ vp install
vp install
$ cp .env.example .env
cp .env.example .env
$ pnpm run db:migrate:local
pnpm run db:migrate:local
$ pnpm run db:seed
pnpm run db:seed
$ pnpm run dev
pnpm run dev
web
http://localhost:3071
api
pnpm -C apps/api dev
background
pnpm -C apps/background dev
providers
env-gated: nothing to configure