{"id":13602118,"url":"https://github.com/oliver-oloughlin/jex","last_synced_at":"2025-05-03T13:31:29.023Z","repository":{"id":245757252,"uuid":"819104410","full_name":"oliver-oloughlin/jex","owner":"oliver-oloughlin","description":"Configure strongly typed API clients","archived":false,"fork":false,"pushed_at":"2025-04-14T19:20:33.000Z","size":118,"stargazers_count":8,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-04-14T19:29:33.225Z","etag":null,"topics":["api","api-client","http","http-client","javascript","jsr","typescript","web"],"latest_commit_sha":null,"homepage":"https://jsr.io/@olli/jex","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/oliver-oloughlin.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2024-06-23T19:40:59.000Z","updated_at":"2025-01-27T19:52:05.000Z","dependencies_parsed_at":"2024-06-23T23:38:00.217Z","dependency_job_id":"ea9c8473-c17a-47bf-8942-3be559a1adba","html_url":"https://github.com/oliver-oloughlin/jex","commit_stats":null,"previous_names":["oliver-oloughlin/jex"],"tags_count":14,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oliver-oloughlin%2Fjex","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oliver-oloughlin%2Fjex/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oliver-oloughlin%2Fjex/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oliver-oloughlin%2Fjex/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/oliver-oloughlin","download_url":"https://codeload.github.com/oliver-oloughlin/jex/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252195741,"owners_count":21709695,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["api","api-client","http","http-client","javascript","jsr","typescript","web"],"created_at":"2024-08-01T18:01:14.508Z","updated_at":"2025-05-03T13:31:28.726Z","avatar_url":"https://github.com/oliver-oloughlin.png","language":"TypeScript","funding_links":[],"categories":["api"],"sub_categories":[],"readme":"# jex\n\n[![Release](https://img.shields.io/github/release/oliver-oloughlin/jex)](https://github.com/oliver-oloughlin/jex/releases)\n[![Score](https://jsr.io/badges/@olli/jex/score)](https://jsr.io/@olli/jex/score)\n[![Tests](https://img.shields.io/github/actions/workflow/status/oliver-oloughlin/jex/test.yml?label=tests)](https://github.com/oliver-oloughlin/jex/actions/workflows/test.yml)\n[![License](https://img.shields.io/github/license/oliver-oloughlin/jex)](https://github.com/oliver-oloughlin/jex/blob/main/LICENSE)\n\n`jex` is a configurable API client that lets you define strongly-typed HTTP\ncalls using a JSON-like schema. Data types can be defined and validated using\nany third-party library of your choosing, such as Zod, or you can use the\nbuilt-in `schema` helper which provides simple type casting. `jex` also provides\na plugin API, enabling easy authentication, logging, request throttling and\nmore.\n\n## Highlights\n\n- Strong typing with smart type inference\n- Flexible plugin API\n- Built-in plugins for authentication, request throttling and retrying\n- Support for logging\n\n## Installation\n\nInstall `jex` on your preferred platform:\n\n```console\ndeno add @olli/jex\n```\n\n```console\nnpx jsr add @olli/jex\n```\n\n```console\npnpm dlx jsr add @olli/jex\n```\n\n```console\nbunx jsr add @olli/jex\n```\n\n## How to use\n\n### Create a basic API client\n\nUsing the built-in schema builder provides type inference only, and does not do\nany runtime validation of data.\n\n```ts\nimport { jex, schema } from \"@olli/jex\"\n\ntype Data = {\n  foo: string\n  bar: number\n}\n\nconst client = jex({\n  baseUrl: \"https://domain.com/api\",\n  endpoints: {\n    \"/foo\": {\n      get: {\n        data: schema\u003cData\u003e(),\n      },\n    },\n  },\n})\n\nconst result = await client[\"/foo\"].get()\nif (result.ok) {\n  // Inferred as { foo: string, bar: number }\n  const data = result.data\n} else {\n  // Inferred as null\n  const data = result.data\n}\n```\n\n### With params, query and body\n\n```ts\nimport { jex, schema } from \"@olli/jex\"\n\nconst client = jex({\n  baseUrl: \"https://domain.com/api\",\n  endpoints: {\n    // Also supports notations: \"/foo/[bar]\", \"/foo/{bar}\", \"/foo/\u003cbar\u003e\"\n    \"/foo/:bar\": {\n      post: {\n        body: schema\u003c{ baz: boolean }\u003e(),\n        query: schema\u003c{ q: string; n?: number }\u003e(),\n      },\n    },\n  },\n})\n\nconst result = await client[\"/foo/:bar\"].post({\n  params: {\n    bar: \"Hello\",\n  },\n  query: {\n    q: \"World\",\n  },\n  body: {\n    baz: true,\n  },\n})\n\nif (result.ok) {\n  // Inferred as null\n  const data = result.data\n}\n```\n\n### Using Zod\n\nUsing a data validation library such as Zod provides both type inference and\nruntime validation of data.\n\n```ts\nimport { jex } from \"@olli/jex\"\nimport { z } from \"zod\"\n\nconst DataSchema = z.object({\n  foo: z.string(),\n  bar: z.number(),\n})\n\nconst client = jex({\n  baseUrl: \"https://domain.com/api\",\n  endpoints: {\n    \"/foo\": {\n      get: {\n        data: DataSchema,\n      },\n    },\n  },\n})\n\nconst result = await client.foo.get()\nif (result.ok) {\n  // Inferred as { foo: string, bar: number }\n  const data = result.data\n} else {\n  // Inferred as null\n  const data = result.data\n}\n```\n\n### Plugins\n\n`jex` also provides a handful of built-in plugins to provide easy logging,\nauthentication and more. Plugins can be applied for all endpoints, all actions\nof a specific endpoint, or for a specific action of a specific endpoint.\n\n```ts\nimport { jex } from \"@olli/jex\"\nimport { example } from \"@olli/jex/example\"\n\nconst client = jex({\n  baseUrl: \"https://domain.com/api\",\n  // Applied for all endpoints and actions\n  plugins: [example()],\n  endpoints: {\n    \"/foo\": {\n      // Applied for all actions of this endpoint\n      plugins: [example()],\n      get: {\n        // Applied for this action only\n        plugins: [example()],\n      },\n    },\n  },\n})\n```\n\n#### Logger\n\nProvides basic logging of outgoing requests and incoming responses.\n\n```ts\nimport { jex } from \"@olli/jex\"\nimport { logger } from \"@olli/jex/logger\"\n\n// With default log function\nconst client = jex({\n  baseUrl: \"https://domain.com/api\",\n  plugins: [logger()],\n  endpoints: {},\n})\n\n// With specified log function\nconst client = jex({\n  baseUrl: \"https://domain.com/api\",\n  plugins: [logger({ fn: console.info })],\n  endpoints: {},\n})\n```\n\n#### Basic Auth\n\nProvides basic authentication by setting the `Authorization` header.\n\n```ts\nimport { jex } from \"@olli/jex\"\nimport { basicAuth } from \"@olli/jex/auth\"\n\nconst client = jex({\n  baseUrl: \"https://domain.com/api\",\n  plugins: [basicAuth({\n    username: \"olli\",\n    password: \"secret123\",\n  })],\n  endpoints: {},\n})\n```\n\n#### API Key Auth\n\nProvides API key authentication using headers by default, or alternatively\nquery.\n\n```ts\nimport { jex } from \"@olli/jex\"\nimport { apiKeyAuth } from \"@olli/jex/auth\"\n\nconst client = jex({\n  baseUrl: \"https://domain.com/api\",\n  plugins: [apiKeyAuth({ apiKey: \"secret_key\" })],\n  endpoints: {},\n})\n```\n\n```ts\nimport { jex } from \"@olli/jex\"\nimport { apiKeyAuth } from \"@olli/jex/auth\"\n\nconst client = jex({\n  baseUrl: \"https://domain.com/api\",\n  plugins: [apiKeyAuth({\n    apiKey: \"secret_key\",\n    apiKeyName: \"X-API-KEY\", // default\n    appId: \"my-app\",\n    appIdName: \"X-APP-ID\", // default\n    strategy: \"query\", // default = \"headers\"\n  })],\n  endpoints: {},\n})\n```\n\n#### Bearer Auth\n\nProvides bearer (token) authentication. Accepts both a static bearer token or\nconfig for fetching a token from a server endpoint.\n\n```ts\nimport { jex } from \"@olli/jex\"\nimport { bearerAuth } from \"@olli/jex/auth\"\n\nconst client = jex({\n  baseUrl: \"https://domain.com/api\",\n  // Static token\n  plugins: [bearerAuth({ token: \"super_secret_token\" })],\n  endpoints: {},\n})\n```\n\n```ts\nimport { jex, schema } from \"@olli/jex\"\nimport { bearerAuth } from \"@olli/jex/auth\"\n\nconst client = jex({\n  baseUrl: \"https://domain.com/api\",\n  // Dynamically fetched token using basic auth\n  plugins: [bearerAuth({\n    tokenUrl: \"https://domain.com/api/token\",\n    tokenSchema: schema\u003c{ token: string; expiresAt: number }\u003e(),\n    mapper: (data) =\u003e data.token,\n    validator: (data) =\u003e data.expiresAt \u003e Date.now(),\n    credentials: {\n      username: \"olli\",\n      password: \"banana123\",\n    },\n  })],\n  endpoints: {},\n})\n```\n\n#### Retry List\n\nRetries failed requests in a progressive manner, following the provided list of\nretry delays, specified in milliseconds.\n\n```ts\nimport { jex } from \"@olli/jex\"\nimport { retryList } from \"@olli/jex/retry\"\n\nconst client = jex({\n  baseUrl: \"https://domain.com/api\",\n  // First waits 500ms, then 1000ms, and then 3000ms between retries.\n  // Returns failed response if last attempt fails\n  plugins: [retryList([500, 1000, 3000])],\n  endpoints: {},\n})\n```\n\n#### Fixed Throttle\n\nThrottles requests based on a fixed interval, specified in milliseconds.\n\n```ts\nimport { jex } from \"@olli/jex\"\nimport { fixedThrottle } from \"@olli/jex/throttle\"\n\nconst client = jex({\n  baseUrl: \"https://domain.com/api\",\n  // Ensures a minimum delay of 1 second between requests\n  plugins: [fixedThrottle(1000)],\n  endpoints: {},\n})\n```\n\n#### Rate Throttle\n\nThrottle requests based on a request rate limit.\n\n```ts\nimport { jex } from \"@olli/jex\"\nimport { rateThrottle } from \"@olli/jex/throttle\"\n\nconst client = jex({\n  baseUrl: \"https://domain.com/api\",\n  // Throttles requests to 100 per minute\n  plugins: [rateThrottle(\"100/min\")],\n  endpoints: {},\n})\n```\n\n#### Default Init\n\nSet default request options that will always be applied for the given plugin\nscope unless overridden.\n\n```ts\nimport { jex } from \"@olli/jex\"\nimport { defaultInit } from \"@olli/jex/init\"\n\nconst client = jex({\n  baseUrl: \"https://domain.com/api\",\n  plugins: [defaultInit({\n    headers: {\n      \"x-client-id\": \"my-app\",\n    },\n  })],\n  endpoints: {},\n})\n```\n\n## Development\n\nAny contributions are welcomed and appreciated. How to contribute:\n\n- Clone this repository\n- Add feature / Refactor\n- Add or refactor tests as needed\n- Ensure code quality (check + lint + format + test) using `deno task prep`\n- Open Pull Request\n\n## License\n\nPublished under\n[MIT License](https://github.com/oliver-oloughlin/jex/blob/main/LICENSE)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Foliver-oloughlin%2Fjex","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Foliver-oloughlin%2Fjex","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Foliver-oloughlin%2Fjex/lists"}