Guides
HTTP endpoints
A guide to adding HTTP APIs alongside your application pages.
Custom HTTP endpoints let an Effront application return JSON alongside its rendered Pages.
These endpoints use the same application services.
The example adds GET /api/greeting to the application from the services guide.
It applies a shared response header to the Page and API.
Define a JSON endpoint
Create src/http.ts:
import { Effect } from "effect";
import { HttpRouter, HttpServerResponse } from "effect/http";
import { Greeting } from "./greeting";
export const GreetingApi = HttpRouter.use(
Effect.fn(function* (router) {
const greeting = yield* Greeting;
yield* router.add(
"GET",
"/api/greeting",
Effect.map(greeting.message("Ada"), (message) => HttpServerResponse.jsonUnsafe({ message })),
);
}),
);Select a path that does not overlap Page or Server Function URLs.
Keep /_effront reserved.
Register the route and its service
In src/entry.effront.tsx, add Layer to the effect import.
Then import GreetingApi.
// src/entry.effront.tsx: replace the effect import.
import { Effect, Layer } from "effect";
// Add to the imports.
import { GreetingApi } from "./http";
// Replace the default export.
const ApplicationLayer = GreetingApi.pipe(Layer.provideMerge(Greeting.layer));
export default EFFRONT.make({ routes, layer: ApplicationLayer });Layer.provideMerge supplies Greeting to the route registration and retains it for the Page.
Open /api/greeting.
Expect status 200, content type application/json, and:
{ "message": "Hello, Ada." }The existing / Page still displays the greeting.
Keep resources request-local
Effront builds the application Layer for each request, including custom HTTP requests. Keep connections and other scoped resources within that request. The request scope lasts through response-body completion, failure, or cancellation.
Add a shared response header
Create src/application-layer.ts:
import { Effect, Layer } from "effect";
import { HttpRouter, HttpServerResponse } from "effect/http";
import { Greeting } from "./greeting";
import { GreetingApi } from "./http";
const GlobalHeaders = HttpRouter.middleware(
(httpEffect) =>
Effect.map(httpEffect, HttpServerResponse.setHeader("x-content-type-options", "nosniff")),
{ global: true },
);
export const ApplicationLayer = Layer.mergeAll(GreetingApi, GlobalHeaders).pipe(
Layer.provideMerge(Greeting.layer),
);In src/entry.effront.tsx, import the shared Layer.
Then remove the local definition and unused imports:
// src/entry.effront.tsx: replace the effect import to remove Layer.
import { Effect } from "effect";
// Replace the GreetingApi import.
import { ApplicationLayer } from "./application-layer";
// Remove the local ApplicationLayer declaration.
export default EFFRONT.make({ routes, layer: ApplicationLayer });Both / and /api/greeting now include x-content-type-options: nosniff.
global: true covers Pages, Server Functions, custom routes, and unmatched requests.
Use scoped Middleware for a policy limited to one Routes group.
Configure asset headers on the host.