Exposing Your Service
HTTP Client
Call external APIs from your service
The HttpClient is a wrapper around native fetch that provides typed shortcuts, automatic OpenTelemetry tracing, timeout handling, and bearer token management. Use it in command and subscription functions to call external APIs without importing framework-specific HTTP libraries.
Basic usage
import { HttpClient } from '@purista/core'
const client = new HttpClient({
baseUrl: 'https://api.example.com',
defaultHeaders: {
'content-type': 'application/json; charset=utf-8',
},
})
// GET request
const user = await client.get('/users/123')
// POST request with typed response
const created = await client.post<{ id: string }>('/users', { email: 'test@example.com' })
// PUT request
await client.put('/users/123', { name: 'Updated' })
// DELETE request
await client.delete('/users/123')
Available methods
| Method | Purpose |
|---|---|
.get(path, options?) | GET request |
.post(path, body, options?) | POST request |
.put(path, body, options?) | PUT request |
.patch(path, body, options?) | PATCH request |
.delete(path, options?) | DELETE request |
.setBearerToken(token) | Set bearer token for all subsequent requests |
Authentication flow
// Login to get a token
const loginResponse = await client.post<{ token: string }>('/login', {
username: 'user',
password: 'secret',
})
// Set bearer token for all following requests
client.setBearerToken(loginResponse.token)
// Now all requests include the Authorization header
const restricted = await client.get('/restricted/path')
OpenTelemetry integration
HTTP requests are automatically added to the current trace. Pass the span processor:
import { SimpleSpanProcessor } from '@opentelemetry/sdk-trace-base'
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'
const openTelemetrySpanProcessor = new SimpleSpanProcessor(
new OTLPTraceExporter({ url: process.env.OTEL_EXPORTER_OTLP_ENDPOINT }),
)
const client = new HttpClient({
spanProcessor: openTelemetrySpanProcessor,
baseUrl: 'https://api.example.com',
})
The request headers include standard OpenTelemetry propagation headers, so the external server can continue the trace.
Error handling
Failed requests throw UnhandledError with detailed context:
try {
await client.get('/might-fail')
} catch (error) {
if (error instanceof UnhandledError) {
console.log(error.data.statusCode) // HTTP status
console.log(error.data.method) // HTTP method
console.log(error.data.url) // Full URL
console.log(error.data.response) // Response body
}
}
Error responses are automatically logged and added to the OpenTelemetry trace.
Timeout configuration
const client = new HttpClient({
baseUrl: 'https://api.example.com',
timeout: 10000, // 10 seconds
})
Using in commands
Register the HTTP client as a resource:
// Step 1: declare the resource type on the builder
export const userServiceV1ServiceBuilder = new ServiceBuilder(myServiceInfo)
.defineResource<'paymentApi', HttpClient>()
// Step 2: inject the instance when starting the service
const userService = await userServiceV1Service.getInstance(eventBridge, {
resources: {
paymentApi: new HttpClient({
baseUrl: config.paymentApiUrl,
defaultHeaders: { 'content-type': 'application/json' },
}),
},
})
Use in commands:
.setCommandFunction(async function (context, payload) {
const paymentResult = await context.resources.paymentApi.post('/charge', {
amount: payload.amount,
currency: payload.currency,
})
return { paymentId: paymentResult.id }
})
When to use HttpClient
- Calling third-party REST APIs from commands or subscriptions
- Webhook integrations
- External authentication providers
- Any HTTP communication that needs OpenTelemetry tracing
Common pitfalls
- Creating a new client per request. Reuse the client instance; it manages connections and tokens.
- Not handling timeouts. External APIs can be slow. Always configure timeout.
- Ignoring error responses. Check for
UnhandledErrorand handle appropriately. - Storing API keys in client config. Use secret stores and set tokens at runtime.
Checklist
- Client is reused across requests (registered as a resource)
- Timeout is configured for external API calls
- Bearer tokens are set dynamically, not hardcoded
- Error handling covers network failures and HTTP errors
- OpenTelemetry span processor is passed for tracing
- Tests mock the client, not the underlying fetch