| name | effect-start-routing |
| description | Use when creating routes in effect-start applications using Route and HyperRoute APIs. |
effect-start Routing
Route File Structure
Organize routes in a directory hierarchy:
src/routes/
āāā route.tsx # handles /
āāā layer.tsx # middleware for all routes
āāā users/
ā āāā route.tsx # handles /users
ā āāā [id]/
ā āāā route.tsx # handles /users/:id
āāā [[404]]/
āāā route.ts # catch-all fallback
In src/routes/tree.ts maintain the tree:
import { Route } from "effect-start"
export default Route.tree({
"*": await import("./layer.tsx").then(v => v.default),
"/": await import("./route.tsx").then(v => v.default),
"/users": await import("./users/route.tsx").then(v => v.default),
"/users/:id": await import("./users/[id]/route.tsx").then(v => v.default),
})
Each route.ts or route.tsx file exports a default route:
import { Route } from "effect-start"
export default Route.get(
Route.json({ users: [] })
)
HTTP Methods
Route.get(...)
Route.post(...)
Route.put(...)
Route.patch(...)
Route.del(...)
Route.head(...)
Route.options(...)
Route.use(...)
Methods can be chained:
export default Route
.get(Route.json({ message: "get" }))
.post(Route.json({ message: "post" }))
Response Formats
Route.text()
Returns plain text (Content-Type: text/plain):
Route.get(Route.text("Hello, world!"))
Route.get(Route.text(function*(ctx) {
return "Dynamic text"
}))
Route.html()
Returns HTML (Content-Type: text/html):
Route.get(Route.html("<h1>Hello</h1>"))
Route.get(Route.html(function*(ctx) {
return `<h1>Hello ${ctx.name}</h1>`
}))
Route.json()
Returns JSON (Content-Type: application/json):
Route.get(Route.json({ message: "Hello" }))
Route.get(Route.json(function*(ctx) {
const data = yield* fetchData()
return { data }
}))
Route.bytes()
Returns binary data (Content-Type: application/octet-stream):
Route.get(Route.bytes(new Uint8Array([1, 2, 3])))
Route.render()
Full control over response format (no default Content-Type):
import { Entity, Route } from "effect-start"
Route.get(Route.render(function*(ctx) {
return Entity.make("<custom>data</custom>", {
headers: { "content-type": "application/xml" }
})
}))
Route.sse()
Server-sent events streaming:
import { Stream } from "effect"
import { Route } from "effect-start"
Route.get(
Route.sse(
Stream.fromIterable([
{ event: "message", data: "Hello" },
{ event: "message", data: "World" },
])
)
)
Entity.make() for Custom Responses
Use Entity.make() for full control over status, headers, and body:
import { Entity, Route } from "effect-start"
Route.get(Route.render(function*() {
return Entity.make("", {
status: 301,
headers: { "location": "/new-path" }
})
}))
Route.get(Route.json(function*() {
return Entity.make({ data: "value" }, {
status: 201,
headers: {
"x-custom": "header",
"cache-control": "no-cache"
}
})
}))
Route.get(Route.render(function*() {
return Entity.make("Not found", {
status: 404,
headers: { "content-type": "text/plain" }
})
}))
Layer Files (Middleware)
layer.tsx files apply middleware to all nested routes:
import { Route } from "effect-start"
import { BunRoute } from "effect-start/bun"
export default Route.use(
Route.html(
BunRoute.bundle(() => import("../app.html"))
),
Route.json(function*(ctx, next) {
const value = yield* next()
return { data: value }
})
)
The next() function calls the inner route handler.
Handler Context
Handlers receive a context object with route descriptors and bindings:
Route.get(Route.json(function*(ctx) {
return { method: ctx.method }
}))
Schema Validation for Headers
import { Schema } from "effect"
import { Route } from "effect-start"
export default Route
.use(
Route.schemaHeaders(
Schema.Struct({
"authorization": Schema.String,
})
)
)
.get(
Route.json(function*(ctx) {
return { auth: ctx.authorization }
})
)
Schema Validation for URL Parameters
import { Schema } from "effect"
import { Route } from "effect-start"
export default Route
.use(
Route.schemaUrlParams(
Schema.Struct({
"id": Schema.NumberFromString,
})
)
)
.get(
Route.json(function*(ctx) {
return { userId: ctx.id }
})
)
Path Patterns
Route.tree({
"/": ...,
"/users": ...,
"/users/:id": ...,
"/files/:path*": ...,
"/docs/:slug+": ...,
"/:id?": ...,
"[[404]]": ...,
"[...rest]": ...,
})
JSX Routes (with HyperRoute)
For JSX routes, use HyperRoute from effect-start/hyper:
import { Route } from "effect-start"
import { HyperRoute } from "effect-start/hyper"
export default Route.get(
HyperRoute.html(function*() {
return (
<div>
<h1>Hello, world!</h1>
</div>
)
})
)
Make sure jsxImportSource is set to effect-start in tsconfig.json
SSE: Server-sent Events
Combine multiple streams for complex real-time updates:
import { Effect, Option, Stream, SubscriptionRef } from "effect"
import { Route } from "effect-start"
Route.get(
Route.sse(
Effect
.gen(function*() {
return Stream.make(1, 2, 3, 4).pipe(
Stream.map((status) => ({
event: "status",
data: status,
})),
)
),
)
Bun HTML bundle layout
Use BunRoute.htmlBundle() to wrap routes in an HTML layout:
import { Route } from "effect-start"
import { BunRoute } from "effect-start/bun"
export default Route.tree({
"*": Route.use(
BunRoute.htmlBundle(() => import("./app.html")),
),
"/": Route.get(Route.html("<h1>Content goes here</h1>")),
})