https://github.com/oliver-oloughlin/jex
Configure strongly typed API clients
https://github.com/oliver-oloughlin/jex
api api-client http http-client javascript jsr typescript web
Last synced: about 1 year ago
JSON representation
Configure strongly typed API clients
- Host: GitHub
- URL: https://github.com/oliver-oloughlin/jex
- Owner: oliver-oloughlin
- License: mit
- Created: 2024-06-23T19:40:59.000Z (almost 2 years ago)
- Default Branch: main
- Last Pushed: 2025-04-14T19:20:33.000Z (about 1 year ago)
- Last Synced: 2025-04-14T19:29:33.225Z (about 1 year ago)
- Topics: api, api-client, http, http-client, javascript, jsr, typescript, web
- Language: TypeScript
- Homepage: https://jsr.io/@olli/jex
- Size: 115 KB
- Stars: 8
- Watchers: 2
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# jex
[](https://github.com/oliver-oloughlin/jex/releases)
[](https://jsr.io/@olli/jex/score)
[](https://github.com/oliver-oloughlin/jex/actions/workflows/test.yml)
[](https://github.com/oliver-oloughlin/jex/blob/main/LICENSE)
`jex` is a configurable API client that lets you define strongly-typed HTTP
calls using a JSON-like schema. Data types can be defined and validated using
any third-party library of your choosing, such as Zod, or you can use the
built-in `schema` helper which provides simple type casting. `jex` also provides
a plugin API, enabling easy authentication, logging, request throttling and
more.
## Highlights
- Strong typing with smart type inference
- Flexible plugin API
- Built-in plugins for authentication, request throttling and retrying
- Support for logging
## Installation
Install `jex` on your preferred platform:
```console
deno add @olli/jex
```
```console
npx jsr add @olli/jex
```
```console
pnpm dlx jsr add @olli/jex
```
```console
bunx jsr add @olli/jex
```
## How to use
### Create a basic API client
Using the built-in schema builder provides type inference only, and does not do
any runtime validation of data.
```ts
import { jex, schema } from "@olli/jex"
type Data = {
foo: string
bar: number
}
const client = jex({
baseUrl: "https://domain.com/api",
endpoints: {
"/foo": {
get: {
data: schema(),
},
},
},
})
const result = await client["/foo"].get()
if (result.ok) {
// Inferred as { foo: string, bar: number }
const data = result.data
} else {
// Inferred as null
const data = result.data
}
```
### With params, query and body
```ts
import { jex, schema } from "@olli/jex"
const client = jex({
baseUrl: "https://domain.com/api",
endpoints: {
// Also supports notations: "/foo/[bar]", "/foo/{bar}", "/foo/"
"/foo/:bar": {
post: {
body: schema<{ baz: boolean }>(),
query: schema<{ q: string; n?: number }>(),
},
},
},
})
const result = await client["/foo/:bar"].post({
params: {
bar: "Hello",
},
query: {
q: "World",
},
body: {
baz: true,
},
})
if (result.ok) {
// Inferred as null
const data = result.data
}
```
### Using Zod
Using a data validation library such as Zod provides both type inference and
runtime validation of data.
```ts
import { jex } from "@olli/jex"
import { z } from "zod"
const DataSchema = z.object({
foo: z.string(),
bar: z.number(),
})
const client = jex({
baseUrl: "https://domain.com/api",
endpoints: {
"/foo": {
get: {
data: DataSchema,
},
},
},
})
const result = await client.foo.get()
if (result.ok) {
// Inferred as { foo: string, bar: number }
const data = result.data
} else {
// Inferred as null
const data = result.data
}
```
### Plugins
`jex` also provides a handful of built-in plugins to provide easy logging,
authentication and more. Plugins can be applied for all endpoints, all actions
of a specific endpoint, or for a specific action of a specific endpoint.
```ts
import { jex } from "@olli/jex"
import { example } from "@olli/jex/example"
const client = jex({
baseUrl: "https://domain.com/api",
// Applied for all endpoints and actions
plugins: [example()],
endpoints: {
"/foo": {
// Applied for all actions of this endpoint
plugins: [example()],
get: {
// Applied for this action only
plugins: [example()],
},
},
},
})
```
#### Logger
Provides basic logging of outgoing requests and incoming responses.
```ts
import { jex } from "@olli/jex"
import { logger } from "@olli/jex/logger"
// With default log function
const client = jex({
baseUrl: "https://domain.com/api",
plugins: [logger()],
endpoints: {},
})
// With specified log function
const client = jex({
baseUrl: "https://domain.com/api",
plugins: [logger({ fn: console.info })],
endpoints: {},
})
```
#### Basic Auth
Provides basic authentication by setting the `Authorization` header.
```ts
import { jex } from "@olli/jex"
import { basicAuth } from "@olli/jex/auth"
const client = jex({
baseUrl: "https://domain.com/api",
plugins: [basicAuth({
username: "olli",
password: "secret123",
})],
endpoints: {},
})
```
#### API Key Auth
Provides API key authentication using headers by default, or alternatively
query.
```ts
import { jex } from "@olli/jex"
import { apiKeyAuth } from "@olli/jex/auth"
const client = jex({
baseUrl: "https://domain.com/api",
plugins: [apiKeyAuth({ apiKey: "secret_key" })],
endpoints: {},
})
```
```ts
import { jex } from "@olli/jex"
import { apiKeyAuth } from "@olli/jex/auth"
const client = jex({
baseUrl: "https://domain.com/api",
plugins: [apiKeyAuth({
apiKey: "secret_key",
apiKeyName: "X-API-KEY", // default
appId: "my-app",
appIdName: "X-APP-ID", // default
strategy: "query", // default = "headers"
})],
endpoints: {},
})
```
#### Bearer Auth
Provides bearer (token) authentication. Accepts both a static bearer token or
config for fetching a token from a server endpoint.
```ts
import { jex } from "@olli/jex"
import { bearerAuth } from "@olli/jex/auth"
const client = jex({
baseUrl: "https://domain.com/api",
// Static token
plugins: [bearerAuth({ token: "super_secret_token" })],
endpoints: {},
})
```
```ts
import { jex, schema } from "@olli/jex"
import { bearerAuth } from "@olli/jex/auth"
const client = jex({
baseUrl: "https://domain.com/api",
// Dynamically fetched token using basic auth
plugins: [bearerAuth({
tokenUrl: "https://domain.com/api/token",
tokenSchema: schema<{ token: string; expiresAt: number }>(),
mapper: (data) => data.token,
validator: (data) => data.expiresAt > Date.now(),
credentials: {
username: "olli",
password: "banana123",
},
})],
endpoints: {},
})
```
#### Retry List
Retries failed requests in a progressive manner, following the provided list of
retry delays, specified in milliseconds.
```ts
import { jex } from "@olli/jex"
import { retryList } from "@olli/jex/retry"
const client = jex({
baseUrl: "https://domain.com/api",
// First waits 500ms, then 1000ms, and then 3000ms between retries.
// Returns failed response if last attempt fails
plugins: [retryList([500, 1000, 3000])],
endpoints: {},
})
```
#### Fixed Throttle
Throttles requests based on a fixed interval, specified in milliseconds.
```ts
import { jex } from "@olli/jex"
import { fixedThrottle } from "@olli/jex/throttle"
const client = jex({
baseUrl: "https://domain.com/api",
// Ensures a minimum delay of 1 second between requests
plugins: [fixedThrottle(1000)],
endpoints: {},
})
```
#### Rate Throttle
Throttle requests based on a request rate limit.
```ts
import { jex } from "@olli/jex"
import { rateThrottle } from "@olli/jex/throttle"
const client = jex({
baseUrl: "https://domain.com/api",
// Throttles requests to 100 per minute
plugins: [rateThrottle("100/min")],
endpoints: {},
})
```
#### Default Init
Set default request options that will always be applied for the given plugin
scope unless overridden.
```ts
import { jex } from "@olli/jex"
import { defaultInit } from "@olli/jex/init"
const client = jex({
baseUrl: "https://domain.com/api",
plugins: [defaultInit({
headers: {
"x-client-id": "my-app",
},
})],
endpoints: {},
})
```
## Development
Any contributions are welcomed and appreciated. How to contribute:
- Clone this repository
- Add feature / Refactor
- Add or refactor tests as needed
- Ensure code quality (check + lint + format + test) using `deno task prep`
- Open Pull Request
## License
Published under
[MIT License](https://github.com/oliver-oloughlin/jex/blob/main/LICENSE)