---
title: "A full-stack web framework built on Effect."
description: "popweb is a full-stack web framework built on Effect: typed failures, services and Schema from the request to the React page, on Cloudflare Workers."
url: "https://web.popcomputer.com/effect"
updated: "2026-10-30"
---

# A full-stack web framework built on Effect.

Yes. @popcomputer/web, popweb for short, is a full-stack web framework built on Effect, and it runs on Cloudflare Workers. Every handler is an Effect: its type names the services it needs and the failures it can end with, and a route accepts it only once those services are provided and those failures answered. Form input, route models, sessions, env and page props are Effect Schemas, so what comes in is checked and what goes to the React page is encoded through its schema. Pages, sign-in, a database and background work come with it.

## What Effect gives a web handler

An action is an ordinary Effect value. It keeps Effect’s success, error and requirement channels, and the framework reads all three. [Actions and routes](https://github.com/PatrickOgilvie/popcomputer-web/blob/main/docs/actions.md) and [Application services](https://github.com/PatrickOgilvie/popcomputer-web/blob/main/docs/services.md) go further.

- Typed failures. A record bound can’t load for the visitor fails as a 404, authorize() as a redirect to sign in or a 403, and validateRequest as the form’s errors, and the framework answers each. A failure of your own, such as a declined payment, has to be answered with Effect.catchTags, often as invalid() beside its form field, or registering the route is a type error that names it.
- Services and layers. An action names the services it uses, such as the database or your own mailer, and the routes accept it only when the app provides them: otherwise the type error lists what’s missing. Your own services are Effect layers given to web.services, and a test swaps one for a fake with Layer.succeed.
- Schema for input. validateRequest decodes route parameters, query and body through an Effect Schema, and drops every key the schema doesn’t declare. The framework’s validators accept form posts as well as JSON.
- Schema for props. Each page’s props are a schema in src/pages.ts, often taken from a table’s columns. A call to render accepts only props that match it, and encodes them through it, so a field the schema doesn’t name, such as a user’s email, never leaves the server.
- Schema for env and route models. src/env.ts declares each var and secret with its schema, checked when an isolate starts. A route model is its table’s row, checked against the table’s columns, and sessions, cached values and queue messages are decoded the same way.

## One action, described

The [README](https://github.com/PatrickOgilvie/popcomputer-web#readme) opens with an example in two parts. A binding, routeBinding on the projects table with ownedBy set to its userId column, says only a project’s owner may load it. The action is an Effect.gen that asks bound for the project and returns render for the Projects/Show page, with the project as its prop.

That’s the whole handler: no query, no ownership check and no try and catch. Anyone else gets the 404 a missing project gets, a signed-out visitor is asked to sign in, and props that don’t match the page’s schema are a type error. [AGENTS.md](https://github.com/PatrickOgilvie/popcomputer-web/blob/main/AGENTS.md) has a complete update action, with its validation, write and redirect.

## How a request flows

Each request takes the same steps, from the route to the response:

- The route. Hono matches a path declared in src/paths.ts. The framework’s middleware runs first, including the check that refuses writes from other sites, and the page cache, which can answer a public page before anything else runs.
- The guards. Layers provided to the route, such as RequireAuthLayer or a membership guard(), run before the action and can turn the visitor away.
- The action. setupWeb builds the request’s Effect runtime with only the services configured for it, and runs the action.
- The schema. validateRequest reads the params, query and body, and returns typed input or sends the form back with its errors.
- The binding. The first call to bound loads each record the path names, with its access rule in the query, so a record the visitor may not see is a 404.
- The response. A call to render encodes the props through the page’s schema and answers as Inertia expects: JSON to an Inertia visit, the HTML shell on a first load. After a write, redirect and back answer with a 303.

## An Effect TypeScript web app on Cloudflare Workers

popweb runs as one Cloudflare Worker, with Effect on every request. The database is D1, or Postgres through Hyperdrive, both through Drizzle, and actions reach it with dbQuery and dbMutation. The data cache keeps its entries in KV, a Workers Rate Limiting binding slows password guessing, and Cron Triggers and Queues run through routes.scheduled and routes.queue, with the app’s services. Vars and secrets are read with config, Effect’s Config named by the Worker’s bindings.

Anything that holds a request’s I/O, such as a D1, KV or Hyperdrive binding, goes in a layer built for each request, because Workers forbid using it from another request. Configuration and clients that open nothing until used are built once per isolate. [Application services](https://github.com/PatrickOgilvie/popcomputer-web/blob/main/docs/services.md) shows both. To start with all of it set up, create an app from the [demo](https://github.com/PatrickOgilvie/popcomputer-web-demo): React pages, D1, Better Auth and tests, ready to deploy.

## Effect’s HttpApi, and effect-cf

Effect has HTTP modules of its own. [HttpApi](https://effect.website/docs/v4/api/effect/http-api/HttpApi), in @effect/platform on Effect 3 and in the effect package as effect/http-api on Effect 4, declares an API as groups of endpoints, each with schemas for its path, query, headers, payload, success and errors. From them it builds a server, a typed client and an OpenAPI description. For a JSON API that stays inside Effect’s own packages, it’s the natural choice, and popweb doesn’t replace it.

popweb is for apps with pages. Its routes run on Hono, not HttpApi, and what it adds is the app around Effect: Inertia and React pages, forms that go back with their errors, Better Auth sessions, Drizzle route models that say who may load them, caching and an [agent fence](https://github.com/PatrickOgilvie/popcomputer-web/blob/main/docs/agent-fence.md). It has [JSON endpoints](https://github.com/PatrickOgilvie/popcomputer-web/blob/main/docs/endpoints.md) too, for an API beside the pages.

[effect-cf](https://github.com/danieljvdm/effect-cf) gives Effect services for Cloudflare Workers and Durable Objects: entrypoints, typed bindings and storage, with D1, Queues, Workflows and WebSockets in its package docs. It goes further into Cloudflare’s own primitives than popweb, whose docs have no API for Durable Objects, Workflows or WebSockets. As its own description says, it’s Effect-native primitives for Workers and their bindings, rather than a framework with pages and sign-in.

## Who it’s for

- TypeScript developers who use Effect, or want to, and would rather not wire Hono, Inertia, Better Auth and Drizzle to it themselves.
- Apps with pages and forms: internal tools, portals, dashboards, products with accounts. It isn’t a replacement for a client-side router, where the browser owns navigation and data fetching.
- Teams building with coding agents. Each task has one API, and a missing service or an unanswered failure is a type error at the route that names it. [Built for coding agents](/coding-agents) has more.
- Not for an API with no pages, where Effect’s own HttpApi may be all you need, or for a team that doesn’t want Effect in its own code, since every action is one.

## Questions people ask

### Do I need to know Effect to use popweb?

To read the code, a little. Every handler wraps an Effect.gen function, which uses yield* where you’d write await, and most of each action is the framework’s own functions: validateRequest, bound, render. You meet more of Effect as an app grows: Schema for forms and pages, catchTags for your own failures, layers for your own services. The README is plain that Effect is a deliberate dependency, most useful when actions, services and background work all use it. [Effect’s site](https://effect.website) says “You can be productive in a few days.” If an AI writes your code, [AGENTS.md](https://github.com/PatrickOgilvie/popcomputer-web/blob/main/AGENTS.md) in the package names the one API for each task.

### Which version of Effect does popweb use?

Effect 4. The package accepts 4.x and is tested with 4.0.5. The old honertia package, and releases before 0.4, ran on Effect 3, and [UPGRADING.md](https://github.com/PatrickOgilvie/popcomputer-web/blob/main/UPGRADING.md) moves an app’s own code from Effect 3 to 4.

### Can I build a JSON API with popweb?

Yes. Each endpoint is declared once, as a contract, with schemas for its query, body and headers, its success, and each failure under its status. The action receives its input decoded, call gives clients and tests a typed way to reach it, and openApi describes every endpoint. [JSON endpoints](https://github.com/PatrickOgilvie/popcomputer-web/blob/main/docs/endpoints.md) shows how.

### Does popweb use Effect’s HttpApi?

No. Its routes run on Hono, and its JSON endpoints are contracts of its own. Your own services are ordinary Effect code, so the rest of Effect is there to use in them.

### What happens to a failure nothing handles?

A typed failure an action doesn’t answer is a type error where its route is registered, naming the failure. One the action can’t answer, such as another system being down, goes through Effect.orDie: the request answers 500, with a generic message in production, and the error observer receives the failure, for Sentry or whatever you report errors with. [Failures an action cannot answer](https://github.com/PatrickOgilvie/popcomputer-web/blob/main/docs/actions.md#failures-an-action-cannot-answer) has the details.

## Read next

- [A Laravel-style framework for Cloudflare Workers.](https://web.popcomputer.com/laravel-for-cloudflare-workers)
- [A web framework built for coding agents.](https://web.popcomputer.com/coding-agents)
