# @popcomputer/web > Effect-native, server-driven web applications on Hono. Pages are React components served over the Inertia protocol, route handlers are Effect actions with typed failures, and sessions, input, route parameters and database rows are parsed with Effect Schema. Better Auth and Drizzle route model binding are optional. Apps run on Cloudflare Workers. Current version: 0.6.0. Peer dependencies: effect ^4.0.0 (stable Effect 4, not 4.0.0-rc.*) and hono ^4. Optional peers: better-auth 1.x, drizzle-orm. ## Read first - [README](https://github.com/PatrickOgilvie/popcomputer-web#readme): setup, actions, validation, route model binding, auth, responses, caching, CLI - [Working example](https://github.com/PatrickOgilvie/popcomputer-web-demo): Better Auth, D1, owner-scoped CRUD, sessions, OpenAPI, portless dev URLs - [Changelog](https://github.com/PatrickOgilvie/popcomputer-web/blob/main/CHANGELOG.md) - [npm](https://www.npmjs.com/package/@popcomputer/web) Prefer these over remembered APIs. The package is 0.x and the names below are current for 0.6.0. ## Set up a new app 1. Create a Bun + TypeScript project with Vite, React, Tailwind CSS and Wrangler (Cloudflare Workers). 2. Install: bun add @popcomputer/web effect hono drizzle-orm @inertiajs/react react react-dom Keep drizzle-orm installed even without a database: Workers builds of 0.6.0 resolve it from the route-binding code. Add better-auth only when the app needs accounts. 3. Describe application types by augmenting the '@popcomputer/web/effect' module: WebBindingsType for Worker bindings, then WebDatabaseType, WebAuthType and WebRouteBindingsType only for features the app uses. 4. In src/app.ts create a Hono app and call setupWeb(app, { version, render }) once. - version: createVersion(manifest), from Vite's build manifest. - render: createTemplate(...). In development load the client from Vite with vite.script('/src/main.tsx', viteOrigin) and vite.hmrHead(viteOrigin); viteOrigin may be a port or an origin URL. - Optional top-level keys: database, schema, bindings, auth, effect, security, errors. 5. Write every route handler as an Effect wrapped in action(...). End each branch with one of: render('Page', props), redirect(url), json(data), jsonOrRender('Page', data), notFound(resource, id), forbidden(message). 6. Validate request bodies with validateRequest(Schema, { errorComponent: 'Page' }); invalid input re-renders that page with field errors. 7. Register routes with effectRoutes(app).get/post/put/delete(path, action, { name }). Give every route a name. 8. Add the Inertia client: createInertiaApp resolving ./pages/**/*.tsx, and a first page component. 9. Inspect and check the app: bunx @popcomputer/web routes --app src/app.ts bunx @popcomputer/web check --app src/app.ts ## Core API - setupWeb(app, config): installs middleware, the Effect runtime and the shared error boundary; returns { app, routes }. - action(effect): marks an Effect as an HTTP action without changing its success, error or requirement types. - authorize() / authorize(predicate): the parsed { user, session }; UnauthorizedError without a user, ForbiddenError when the predicate is false. - bound('name'): the schema-checked model loaded by a {name} route placeholder; register schemas in setupWeb({ bindings }). Scope nested bindings with routeBinding(schema, { scope }); ambiguous nesting fails closed. - validateRequest(schema, options): branded, validated input. dbMutation(db, values, fn) and dbTransaction accept only validated or asTrusted(...) values; database failures stay typed. - cache(key, effect, schema, { ttl, swr, version }) from '@popcomputer/web/cache': schema-validated KV cache. - background(effect): work that outlives the response (waitUntil on Workers). - effectifyBetterAuth(auth) from '@popcomputer/web/auth': Better Auth's API as Effects with typed failures. - EffectErrorObserverService: report failures to Sentry, PostHog or logs without changing the failure channel. ## CLI bunx @popcomputer/web routes --app src/app.ts --json bunx @popcomputer/web check --app src/app.ts bunx @popcomputer/web generate:action projects/create --method POST --path /projects bunx @popcomputer/web generate:crud projects bunx @popcomputer/web generate:openapi --app src/app.ts --output openapi.json ## Rules for agents - Keep expected failures in Effect's typed error channel; do not throw inside actions. - Parse every external value (request input, route parameters, sessions, rows, cached data) with Effect Schema. - Choose page props deliberately; share only the user fields the client needs (setupWeb({ auth: { share } })). - Ask before adding authentication, a database, or deploying.