Skip to main content
basicHandler creates path-based HTTP handlers that can return responses synchronously or as Effect computations.

Usage

import { basicHandler } from 'ff-serv'
import { Effect } from 'effect'

const handler = basicHandler('/health', () => {
  return new Response('OK')
})

Path Matching

Paths can be defined as strings or custom matcher functions:

String Paths

basicHandler('/api/users', (request) => {
  return new Response('User list')
})
Path matching is exact - /api/users will not match /api/users/123.

Custom Matcher Function

basicHandler(
  (url) => url.pathname.startsWith('/api/'),
  (request) => {
    return new Response('API endpoint')
  }
)

Handler Functions

Synchronous Response

Return a Response directly:
basicHandler('/ping', () => {
  return new Response('pong')
})

Effect-based Response

Return an Effect for async operations or context requirements:
import { HttpClient } from '@effect/platform'

basicHandler('/proxy', (request) =>
  Effect.gen(function* () {
    const client = yield* HttpClient.HttpClient
    const response = yield* client.get('https://api.example.com/data')
    return new Response(yield* response.text)
  })
)

Type Signatures

// Synchronous handler (no requirements)
function basicHandler(
  path: `/${string}` | ((url: URL) => boolean),
  fn: (request: Request) => Response
): Handler<'basicHandler', never>

// Effect handler (with requirements R)
function basicHandler<R>(
  path: `/${string}` | ((url: URL) => boolean),
  fn: (request: Request) => Effect.Effect<Response, unknown, R>
): Handler<'basicHandler', R>

Complete Example

import { createFetchHandler, basicHandler } from 'ff-serv'
import { Effect, Context } from 'effect'

// Define a service
class Database extends Context.Tag('Database')<
  Database,
  { query: (sql: string) => Effect.Effect<unknown[]> }
>() {}

const program = Effect.gen(function* () {
  const fetch = yield* createFetchHandler([
    // Simple sync handler
    basicHandler('/health', () => new Response('OK')),

    // Handler with URL matcher
    basicHandler(
      (url) => url.pathname.startsWith('/api/'),
      () => new Response('API route')
    ),

    // Handler requiring Database service
    basicHandler('/users', () =>
      Effect.gen(function* () {
        const db = yield* Database
        const users = yield* db.query('SELECT * FROM users')
        return Response.json(users)
      })
    ),
  ])

  Bun.serve({ port: 3000, fetch })
})

// Provide the Database service
Effect.runPromise(
  program.pipe(
    Effect.provideService(Database, {
      query: (sql) => Effect.succeed([]),
    })
  )
)

Request Access

The handler function receives the incoming Request object:
basicHandler('/echo', async (request) => {
  const body = await request.text()
  return new Response(body)
})
When using Effect-based handlers, you can return Promise<Response> directly without wrapping in Effect.tryPromise - the framework handles this automatically.

Error Handling

Errors in handlers are caught automatically by createFetchHandler and return a 500 response. See Fetch Handler for details.