Declarative API versioning at the edge.
API Thing manages multiple versions, defined as OpenAPI specs, with declarative transformations courtest of JSONata. Running at the edge on Cloudflare Workers.
- Declarative Transformations: Use JSONata expressions instead of imperative code
- OpenAPI-Driven: Generate transformations automatically from OpenAPI spec diffs
- High Performance: <1ms transformation overhead for simple mappings
- Edge Deployment: Runs on Cloudflare Workers with global distribution
- Hot Reloadable: Update transformations without redeploying the worker
- Type Safe: Full TypeScript support with type definitions
- Comprehensive Testing: Unit and integration tests included
- Developer Friendly: Rich CLI tools for generation, validation, and deployment
┌─────────────┐ ┌──────────────────┐ ┌─────────────┐
│ Client │ │ CF Worker │ │ Upstream │
│ (v1 API) │─────▶│ + JSONata │─────▶│ API (v2) │
│ │◀─────│ Transformations │◀─────│ │
└─────────────┘ └──────────────────┘ └─────────────┘
│
▼
┌─────────────────┐
│ Cloudflare KV │
│ (Transform │
│ Configs) │
└─────────────────┘
- Node.js 18+
- Cloudflare account with Workers access
- Wrangler CLI installed (
pnpm install -g wrangler)
# Clone or navigate to the project
cd api-thing
# Install dependencies
pnpm install
# Login to Cloudflare
wrangler login# Run locally
pnpm run dev
# Test the API
curl http://localhost:8787/users/1
curl -H "API-Version: v1" http://localhost:8787/users/1# Validate transformations
pnpm run validate:transformations
# Build the worker
pnpm run build
# Deploy to dev environment
pnpm run deploy
# Deploy to production
pnpm run deploy:productionTransform v1 field names to v2:
{
"id": user_id,
"name": full_name,
"email": email_address
}
{
"user": {
"id": $.userId,
"profile": {
"name": $.name,
"email": $.email
}
},
"metadata": {
"created": $toMillis($.createdAt),
"active": $.status = "active"
}
}
$map(users, function($user) {
{
"id": $user.user_id,
"name": $user.full_name,
"isActive": $user.status = "active"
}
})
This sample API Thing configuration comes packaged with the service for demonstration purposes. This is a proprietary config which references JSONata transformation files.
{
"version": "1.0.0",
"defaultVersion": "v2",
"upstreamVersion": "v2",
"transformations": {
"v1": {
"request": {
"expression": "kv:transformations/v1-to-v2-request.jsonata",
"description": "Transform v1 request to v2",
"cacheTtl": 3600
},
"response": {
"expression": "kv:transformations/v2-to-v1-response.jsonata",
"description": "Transform v2 response to v1",
"cacheTtl": 3600
}
}
},
"routing": {
"/users": ["v1", "v2"],
"/posts": ["v1", "v2"]
}
}API Thing comes with a CLI for generating transformations.
Automatically generate JSONata expressions by comparing OpenAPI specs:
pnpm run generate:transformations openapi/v1.yaml openapi/v2.yamlOutput:
transformations/v1-to-v2-request.jsonatatransformations/v2-to-v1-response.jsonatatransformations/config.json
Test JSONata expressions with sample data:
pnpm run validate:transformationsUpload configurations to KV and deploy worker:
# Deploy to dev
pnpm run upload:config -- --env dev
pnpm run deploy
# Deploy to production
pnpm run upload:config -- --env production
pnpm run deploy:productionThe gateway detects the API version from multiple sources (in order of priority):
- Header:
API-Version: v1orX-API-Version: v1 - Query Parameter:
?api-version=v1or?version=v1 - Path Prefix:
/v1/users(extractsv1) - Default: Falls back to
defaultVersionfrom config
api-thing/
├── src/
│ ├── worker.ts # Main Cloudflare Worker
│ ├── transformer.ts # JSONata transformation engine
│ ├── config-loader.ts # KV configuration loader
│ └── types.ts # TypeScript definitions
├── transformations/
│ ├── config.json # Transformation configuration
│ ├── v1-to-v2-request.jsonata
│ └── v2-to-v1-response.jsonata
├── tools/
│ ├── generate-jsonata.ts # OpenAPI → JSONata generator
│ ├── validate-transformations.ts
│ └── deploy.ts # Deployment automation
├── tests/
│ ├── transformer.test.ts
│ └── config-loader.test.ts
├── openapi/ # OpenAPI specifications
├── wrangler.toml # Cloudflare Workers config
├── package.json
└── README.md
# Run all tests
pnpm test
# Watch mode
pnpm run test:watch
# Integration tests
pnpm run test:integration
# Type checking
pnpm run type-checkThe gateway includes built-in observability:
- Request ID tracking (
X-Request-IDheader) - Version usage metrics
- Transformation performance metrics
- Error logging with context
- Cloudflare Analytics integration
- Path navigation:
address.city,users[0].name - Array operations:
$map(),$filter(),$reduce() - Predicates:
users[age > 18] - Aggregation:
$sum(),$count(),$average() - String functions:
$uppercase(),$substring(),$contains() - Conditionals:
status = 'active' ? 'yes' : 'no' - Object construction:
{ "newField": oldField } - Type coercion:
$number(),$string(),$boolean()
Add custom JSONata functions in your configuration:
{
"customFunctions": {
"formatDate": "function($date) { $fromMillis($toMillis($date), '[Y0001]-[M01]-[D01]') }"
}
}Different transformations based on data:
$exists(premium) ?
{ "type": "premium", "features": premium.features }
:
{ "type": "basic", "features": [] }
{
"users": users.$map(function($u) {
{
"id": $u.userId,
"posts": $u.posts.$map(function($p) {
{ "title": $p.postTitle, "date": $p.createdAt }
})
}
})
}
Check the logs for detailed error messages:
wrangler tail --env productionClear the cache by reloading the configuration:
# This will be handled automatically on next request
# Or manually trigger via API endpoint
curl -X POST https://your-worker.workers.dev/__reload- Keep expressions simple: Complex expressions slow down transformation
- Use caching: Set appropriate
cacheTtlvalues - Minimize KV calls: Store expressions inline when possible
- Profile transformations: Use the metrics in responses
- Fork the repository
- Create a feature branch
- Make your changes
- Add tests
- Run validation:
pnpm test && pnpm run validate:transformations - Submit a pull request
- JSONata Documentation
- JSONata Exerciser - Interactive playground
- Cloudflare Workers Docs
- OpenAPI Specification
MIT
For issues and questions:
- Open an issue on GitHub
- Check the implementation_guide.md for detailed architecture
- Review example transformations in transformations/