Skip to content
effective-rsc
DocumentationIndex

Composing ERSC and userland HTTP

ERSC concerns and native HTTP routes share one application Layer.

10_application-layer.tsx
import { Effect, Layer } from 'effect';
import { HttpRouter, HttpServerResponse } from 'effect/unstable/http';
import { Application } from 'effective-rsc';

import { Catalog } from '../02-services/10_catalog';

const CatalogApi = HttpRouter.use(
  Effect.fnUntraced(function* (router) {
    const catalog = yield* Catalog;
    const response = Effect.map(catalog.featured, HttpServerResponse.jsonUnsafe);
    yield* router.add('GET', '/api/catalog', response);
  }),
);

const GlobalHeaders = HttpRouter.middleware(
  (httpEffect) =>
    Effect.map(httpEffect, HttpServerResponse.setHeader('x-content-type-options', 'nosniff')),
  { global: true },
);

const ApplicationLayer = Layer.mergeAll(CatalogApi, GlobalHeaders).pipe(
  Layer.provideMerge(Catalog.layer),
);

const ERSC = Application.ersc<Catalog>();

const RootLayout = ERSC.Layout.make({
  render: ({ children }) =>
    Effect.succeed(
      <html lang='en'>
        <body>{children}</body>
      </html>,
    ),
});

const CatalogPage = ERSC.Page.make({
  render: Effect.fn('CatalogPage.render')(function* () {
    const catalog = yield* Catalog;
    const items = yield* catalog.featured;
    return (
      <ul>
        {items.map((item) => (
          <li key={item.name}>
            {item.name}: ${item.price}
          </li>
        ))}
      </ul>
    );
  }),
});

export default ERSC.make({
  routes: ERSC.Routes.make({ layout: RootLayout }).page('/catalog', CatalogPage),
  layer: ApplicationLayer,
});