Derive type-safe fetch clients from purescript-yoga-http-api route definitions.
spago install yoga-fetch-omOptional query examples below use justifill:
spago install justifillVersion 0.8 exposes optional query parameters as explicit Maybe fields. Calls that passed plain values no longer compile:
-- 0.7: offset omitted
users <- api.listUsers { limit: 10 }
-- 0.8: every optional field is explicit
users <- api.listUsers { limit: Just 10, offset: Nothing }
allUsers <- api.listUsers { limit: Nothing, offset: Nothing }Import Maybe(..) from Data.Maybe when constructing these records directly. Alternatively, justifill wraps supplied values in Just and fills omitted fields with Nothing:
-- offset remains omitted at the call site
users <- api.listUsers (justifill { limit: 10 })Path parameters and request bodies keep their existing argument shapes.
import Yoga.Fetch.Om.Simple (get, post)
type User = { id :: Int, name :: String, email :: String }
main = do
-- Simple GET request
user <- get @User "https://api.example.com/users/42" {}
log user.name
-- Simple POST request with JSON body
newUser <- post @User "https://api.example.com/users" {} { name: "Alice", email: "alice@example.com" }
log $ "Created user with ID: " <> show newUser.idimport Justifill (justifill)
type UserAPI =
{ health ::
Route GET "health" {}
( ok :: { body :: { status :: String } } )
, getUser ::
Route GET ("users" / "id" : Int) {}
( ok :: { body :: User }
, notFound :: { body :: ErrorMessage }
)
, listUsers ::
Route GET ("users" :? { limit :: Int, offset :: Int }) {}
( ok :: { body :: Array User } )
, createUser ::
Route POST "users" { body :: JSON CreateUserRequest }
( created :: { body :: User }
, badRequest :: { body :: ErrorMessage }
)
, updateUser ::
Route PUT ("users" / "id" : Int) { body :: JSON UpdateUserRequest }
( ok :: { body :: User }
, notFound :: { body :: ErrorMessage }
, badRequest :: { body :: ErrorMessage }
)
, deleteUser ::
Route DELETE ("users" / "id" : Int) {}
( noContent :: { body :: {} }
, notFound :: { body :: ErrorMessage }
)
}
api = client @UserAPI "https://api.example.com"
-- Generated methods use ordinary function application.
health <- api.health
user <- api.createUser { name: "Alice", email: "alice@example.com" }
# handleErrors
{ badRequest: \err -> do
log $ "Validation error: " <> err.error
throw err
}
-- Path parameters and request bodies remain separate arguments.
updated <- api.updateUser
{ id: user.id }
{ name: "Alice Updated", email: user.email }
# handleErrors
{ notFound: \_ -> throw userNotFound
, badRequest: \err -> throw validationError
}
-- Optional query parameters are explicit `Maybe` fields. Justifill can wrap
-- supplied values in `Just` and fill omitted fields with `Nothing`.
users <- api.listUsers (justifill { limit: 10 })
-- The equivalent call without Justifill:
allUsers <- api.listUsers { limit: Nothing, offset: Nothing }See the test files for complete, runnable examples with all imports:
test/Simple.Spec.purs- Basic GET/POST/PUT/PATCH/DELETE requeststest/Complete.Example.purs- Full CRUD API with error handlingtest/BuildUrl.Spec.purs- Path parameters and query stringstest/Variant.Spec.purs- Response variant handlingtest/SplitParams.Spec.purs- Parameter extraction patterns
- Path parameters:
/users/:idrequires{ id :: Int }; names and values are percent-encoded - Query parameters: Type-safe, percent-encoded query strings with explicit
Maybefields for optional values - Request bodies:
JSON,PlainText, and URL-encodedFormData - Response bodies: Parsed JSON,
PlainTextasString, andStreaming aasStrom {} () a - Error handling: Exhaustive pattern matching on variants
Define your API once, use it everywhere:
-- Server (yoga-fastify-om)
server = buildServer apiRoutes handlers
-- Client (yoga-fetch-om)
apiClient = client @apiRoutes baseUrlChanges to routes automatically update both client and server!
No manual client code:
- ✅ URL building with segment-safe path substitution
- ✅ Query construction that preserves existing queries and fragments
- ✅ JSON, plain-text, and URL-encoded request serialization
- ✅ JSON and plain-text response decoding
- ✅ Incremental UTF-8 and binary response streams
- ✅ Status code → variant mapping
Works seamlessly with the Om monad:
getUserProfile :: Om AppContext AppErrors User
getUserProfile = do
{ api, userId } <- ask
api.getUser { id: userId }
# handleErrors { notFound: \_ -> throw { userNotFound: userId } }The library uses PureScript's type system to:
-
Extract parameters from route definitions at compile time
- Path params:
"users" / "id" : Int→{ id :: Int } - Optional query params:
:? { limit :: Int }→{ limit :: Maybe Int } - Required query params:
:? { limit :: Required Int }→{ limit :: Int } - Body params:
{ body :: JSON User }→User
- Path params:
-
Build URLs automatically
- Pattern:
/users/:id+ params:{ id: 42 }→/users/42 - Query:
?limit=10&offset=20
- Pattern:
-
Make requests with
js-fetchandjs-promise-aff -
Parse responses by mapping status codes to variant labels
200→"ok",404→"notFound", etc.
All automatically based on your route types!
client :: forall @routes. String -> Record clientsDerives a record of client functions from a record of routes using Visible Type Application.
Parameters:
@routes- Your API route type (provided via VTA syntax)baseUrl- Base URL (e.g.,"https://api.example.com")
Returns: Record where each route becomes a function returning Om context errors result
Example:
api = client @UserAPI "https://api.example.com"- ✅ Core client derivation with VTA syntax
- ✅ All HTTP methods (GET, POST, PUT, PATCH, DELETE)
- ✅ Path, query, and body parameters
- ✅ Request/response headers
- ✅ JSON encoding/decoding
- ✅ FormData support
- ✅ Variant response handling
- ✅ Om monad integration
bun test
bun run test:compile-fail
bun run coverageThe suite combines example-based specifications with QuickCheck properties for URL encoding, suffix preservation, repeated parameters, optional fields, and query-separator invariants.
The coverage command uses compiler source maps to report PureScript source
coverage, includes JavaScript FFI modules, enforces regression thresholds, and
writes HTML and LCOV reports to coverage/.
MIT
- purescript-yoga-http-api - HTTP API type definitions
- purescript-yoga-fastify-om - Server-side counterpart
- purescript-yoga-json - JSON serialization
- purescript-yoga-om - Om monad