Skip to content

Workers

Serve an API from F#, or place F# code in front of an existing site to rewrite and cache its pages. A Worker is a module that exports a handler with a fetch function, and Fable compiles that handler to the default export of a JavaScript module.

Health Check

The smallest Worker responds to every request with ok. [<ExportDefault>] marks the handler as the module's default export, and U2.Case2 wraps a finished response. Here all four type arguments of ExportedHandler are obj, the F# type for any value.

open Fable.Core

module Workers = FSharp.CloudEdge.Runtime.Workers

[<ExportDefault>]
let worker: Workers.ExportedHandler<obj, obj, obj, obj> =
    Workers.ExportedHandler.Create(fetch = fun request env ctx ->
        U2.Case2(Workers.Exports.Response.Create("ok")))
Emitted JavaScript
export const worker = {
    fetch: (request, env, ctx) => (new globalThis.Response("ok")),
};

export default worker;

API Router

The router matches the request method and path together. Response.json serializes an anonymous record, and ResponseInit sets the status code of each error response.

open Fable.Core

module Workers = FSharp.CloudEdge.Runtime.Workers

let jsonWithStatus (status: float) body =
    let init = Workers.ResponseInit.Create(status = status)
    Workers.Exports.Response.json(body, U2.Case2 init)

[<ExportDefault>]
let worker: Workers.ExportedHandler<obj, obj, obj, obj> =
    Workers.ExportedHandler.Create(fetch = fun request _ _ ->
        let url = Workers.Exports.URL(U2.Case1 request.url)
        let response =
            match request.``method``, url.pathname with
            | "GET", "/" -> Workers.Exports.Response.Create("Welcome to the API")
            | "GET", "/api/greeting" ->
                let name = url.searchParams.get "name" |> Option.defaultValue "world"
                Workers.Exports.Response.json {| greeting = $"Hello, {name}" |}
            | _, "/api/greeting" -> jsonWithStatus 405. {| error = "Use GET" |}
            | _ -> jsonWithStatus 404. {| error = "Not found" |}
        U2.Case2 response)

Visitor Greeting

request.cf holds properties that Cloudflare's network provides about each incoming request, such as its city. Both request.cf and its city property are F# options. Cloudflare's Request documentation states that request.cf is unavailable in the Workers dashboard and the Playground preview editor.

open Fable.Core

module Workers = FSharp.CloudEdge.Runtime.Workers

[<ExportDefault>]
let worker: Workers.ExportedHandler<obj, obj, obj, obj> =
    Workers.ExportedHandler.Create(fetch = fun request _ _ ->
        let city =
            request.cf
            |> Option.bind (fun cf -> cf.city)
            |> Option.defaultValue "your city"
        U2.Case2(Workers.Exports.Response.Create($"Hello to everyone in {city}")))

API Proxy

The handler's second argument is the Worker's environment. Declare its type as an F# interface such as Env, and pass that interface as the first type argument of ExportedHandler. This proxy sends a GET to UPSTREAM_URL with the path and query string of each request. That GET has an Authorization header with the API_KEY secret, so only the upstream API receives the key. fetch returns a promise, which the handler wraps in U2.Case1.

open Fable.Core

module Workers = FSharp.CloudEdge.Runtime.Workers

type Env =
    abstract UPSTREAM_URL: string
    abstract API_KEY: string

[<ExportDefault>]
let worker: Workers.ExportedHandler<Env, obj, obj, obj> =
    Workers.ExportedHandler.Create(fetch = fun request env _ ->
        let url = Workers.Exports.URL(U2.Case1 request.url)
        let upstream = env.UPSTREAM_URL + url.pathname + url.search
        let auth = {| Authorization = $"Bearer {env.API_KEY}" |}
        let init = Workers.RequestInit.Create(headers = auth)
        Workers.Exports.fetch(U3.Case1 upstream, init) |> U2.Case1)

Needs a secret named API_KEY and an environment variable named UPSTREAM_URL. Worker Upload shows how to declare these bindings.

Emitted JavaScript
import { concat } from "./fable_modules/fable-library-js.5.13.0/String.js";

export const worker = {
    fetch: (request, env, _arg) => {
        const url = new URL(request.url);
        const upstream = (env.UPSTREAM_URL + url.pathname) + url.search;
        const auth = {
            Authorization: concat("Bearer ", env.API_KEY),
        };
        return fetch(upstream, {
            headers: auth,
        });
    },
};

export default worker;

Announcement Banner

HTMLRewriter runs a handler on each element that matches a selector. This Worker fetches the requested page from ORIGIN and adds a banner at the start of its <body>. With html = true, prepend inserts the banner as HTML instead of escaped text. The element handler is synchronous, so it returns None in place of a promise.

open Fable.Core

module Workers = FSharp.CloudEdge.Runtime.Workers

type Env =
    abstract ORIGIN: string

let banner = """<p class="banner">Orders ship free this week.</p>"""
let asHtml = Workers.ContentOptions.Create(html = true)

let addBanner =
    Workers.HTMLRewriterElementContentHandlers.Create(element = fun body ->
        body.prepend(U3.Case1 banner, asHtml) |> ignore
        None)

[<ExportDefault>]
let worker: Workers.ExportedHandler<Env, obj, obj, obj> =
    Workers.ExportedHandler.Create(fetch = fun request env _ ->
        async {
            let url = Workers.Exports.URL(U2.Case1 request.url)
            let pageUrl = env.ORIGIN + url.pathname + url.search
            let! page = Workers.Exports.fetch(U3.Case1 pageUrl) |> Async.AwaitPromise
            return Workers.Exports.HTMLRewriter().on("body", addBanner).transform page
        }
        |> Async.StartAsPromise
        |> U2.Case1)

Needs an environment variable named ORIGIN that holds your site's address, such as https://www.example.com.

Page Cache

caches.default is the global cache of the Cache API. This Worker returns the cached copy of a page when match finds one. On a miss it fetches the page from ORIGIN and stores a copy with put. According to Cloudflare's Cache documentation, each data center has its own cache contents.

open Fable.Core

module Workers = FSharp.CloudEdge.Runtime.Workers

type Env =
    abstract ORIGIN: string

[<ExportDefault>]
let worker: Workers.ExportedHandler<Env, obj, obj, obj> =
    Workers.ExportedHandler.Create(fetch = fun request env _ ->
        async {
            let cache = Workers.Exports.caches.``default``
            let! cached = cache.``match``(U3.Case1 request.url) |> Async.AwaitPromise
            match cached with
            | Some response -> return response
            | None ->
                let url = Workers.Exports.URL(U2.Case1 request.url)
                let pageUrl = env.ORIGIN + url.pathname + url.search
                let! page = Workers.Exports.fetch(U3.Case1 pageUrl) |> Async.AwaitPromise
                do! cache.put(U3.Case1 request.url, page.clone()) |> Async.AwaitPromise
                return page
        }
        |> Async.StartAsPromise
        |> U2.Case1)

Needs the same ORIGIN environment variable as Announcement Banner.

Library Table

Library npm package What it covers
Runtime.Workers @cloudflare/workers-types 5.20260906.1 Runtime API and binding types

NuGet packages

Runtime.Workers 0.1.0.

See installation and release availability.

Edit this page