{"id":15623819,"url":"https://github.com/mrkmg/type-rest","last_synced_at":"2026-01-20T11:08:06.550Z","repository":{"id":33918996,"uuid":"155400376","full_name":"mrkmg/type-rest","owner":"mrkmg","description":"A simple fetch wrapper made for TypeScript which allows developers to create and distribute a typed interface to their APIs.","archived":false,"fork":false,"pushed_at":"2023-03-04T03:03:33.000Z","size":852,"stargazers_count":2,"open_issues_count":5,"forks_count":0,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-02-13T05:18:36.430Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"https://www.npmjs.com/package/type-rest","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mrkmg.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"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}},"created_at":"2018-10-30T14:34:21.000Z","updated_at":"2021-07-02T05:20:46.000Z","dependencies_parsed_at":"2024-10-22T22:05:16.542Z","dependency_job_id":null,"html_url":"https://github.com/mrkmg/type-rest","commit_stats":null,"previous_names":[],"tags_count":6,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mrkmg%2Ftype-rest","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mrkmg%2Ftype-rest/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mrkmg%2Ftype-rest/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mrkmg%2Ftype-rest/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mrkmg","download_url":"https://codeload.github.com/mrkmg/type-rest/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247563934,"owners_count":20958971,"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":[],"created_at":"2024-10-03T09:59:01.713Z","updated_at":"2026-01-20T11:08:01.506Z","avatar_url":"https://github.com/mrkmg.png","language":"TypeScript","readme":"Type Rest\n============\n\nA simple fetch wrapper made for TypeScript which allows developers to\ncreate and distribute a typed interface to their APIs.\n\n## Quick Start\n\nFirst, create an API instance and Spec:\n\napi.ts\n```typescript\nimport {typeRest, WithNone, WithBody, WithQuery, IHook} from \"type-rest\";\n\nconst loginHook: IHook = {\n     hook: (ev) =\u003e {\n         if (!ev.response.result) return Promise.reject(ev.response.error);\n         ev.instance._options.params.headers.token = ev.response.token; \n     },\n     method: \"POST\",\n     route: \"/authentication/\",\n };\n\nconst logoutHook: IHook = {\n    hook: (ev) =\u003e {\n        delete ev.instance._options.params.headers.token;\n    },\n    method: \"DELETE\",\n    route: \"/authentication/\",\n};\n\nexport const Api = typeRest\u003cApiRoute\u003e(\"https://some-url/api\", {\n    hooks: [loginHook, logoutHook],\n    params: {\n        mode: \"cors\",\n    }\n});\n\nexport interface ApiRoute {\n    authentication: AuthenticationRoute;\n    todos: TodosRoute;\n}\n\nexport interface AuthenticationRoute {\n    Get: WithNone\u003c{authenticated: boolean}\u003e;\n    Post: WithBody\u003c{username: string, password: string}, {result: boolean, token?: string, error?: string}\u003e;\n    Delete: WithNone\u003cvoid\u003e;\n}\n\nexport interface TodosRoute {\n    Get: WithQuery\u003c{page: number, limit?: number}, Todo[]\u003e;\n    Post: WithBody\u003cPick\u003cTodo, Exclude\u003ckeyof Todo, \"id\" | \"completed\"\u003e\u003e, Todo\u003e;\n    [todoId: number]: TodoRoute;\n}\n\nexport interface TodoRoute {\n    Get: WithNone\u003cTodo\u003e;\n    Patch: WithBody\u003cPartial\u003cTodo\u003e, Todo\u003e;\n    Delete: WithNone\u003cvoid\u003e;\n}\n\nexport interface Todo {\n    id: number;\n    date: string;\n    title: string;\n    completed: boolean;\n}\n```\n\n## Intended Use\n\nType Rest is intended to be used to provide a simple to use interface\nto a well defined JSON-based API. All endpoints, query params, and body\nparams can be typed out via TypeScript and the provided types, and then\npassed to an instance of type-rest which will then convert those typed\nroutes into callable functions.\n\nType Rest also provides a hook system to trigger actions before and\nafter a request. The hooks can modify requests as well as trigger\nside-effects with the response.\n\nIn order to provide a typed interface to your api, you must first build\nyour API definition. This is done by using the included helper \ndefinitions.\n\nUnder the hood, Type Rest uses fetch with one major difference. All\nnon-successful calls are rejected. This includes 4xx and 5xx server \nerrors.\n\n## How to Use\n\nOnce you have a defined API and exported an instance of Type Rest with\nyour API definition, you or other developers, will be able to call \nendpoints to your api.\n\nFrom the example above:\n\ntest.ts\n```typescript\nimport {Api} from \"./api\";\n\nasync function testApi() {\n    const authStatus = await Api.authentication.Get();\n    \n    if (!authStatus.authenticated) {\n        await Api.authentication.Post({username: \"test\", password: \"test\"});\n        // Due to the hook define, this will add the appropriate token header to all future API calls\n    }\n    \n    const todos = await Api.todos.Get({page: 2}); // List page two\n    const todo = await Api.todos[1].Get(); // Get to-do with ID 1\n    await Api.todos[1].Delete(); // Delete to-do with ID 1\n}\n\n```\n\n## Building the API Definition\n\nOne of the core concepts of Type Rest is fully defining all the input\nand output types of the restful calls. Using the powerful type system\nbuilt into TypeScript combined with the flexibility of Type Rest, \nalmost any situation can be accounted for. \n\nThere 4 concepts to creating your Typed API. First is the routes,\nsecond are the end points, third are the input and output data, and finally \nthe instance. Routes are paths of your api, end points \nare the HTTP verbs, data is the request and response data, and the\ninstance is what brings all those together into a usable api.\n\n### Defining your api\n\nLets take the following API as an example:\n\n```text\nROOT    https://awesome-app/api/v1/\n\nAuthentication\n\nGET /authentication\nGet authentication status\n\nPOST /authentication\nAuthenticate with the api\n\nDELETE /authentication\nRemove authentication\n\nGET /todos\nGet a list of todos\n\nGET /todos/{id}\nGet a single todo\n\nPOST /todos\nCreate a todo\n\nPATCH /todos/{id}\nUpdate a todo\n\nDELETE /todos/{id}\nDelete a todo\n```\n\nUsing the list of end points above, we can create all the definitions we need.\n\nLooking at the api, we have two first-level routes: \"authentication\" and \n\"todos\". Lets start with the authentication route.\n\nWithin authentication, we have 3 end-points, \"GET, POST, and DELETE\". We can\ndirectly type those.\n\nauthentication-route.ts\n```typescript\nimport {WithBody, WithNone} from \"type-rest\";\n\nexport interface IAuthenticationRoute {\n    Get: WithNone\u003c{authenticated: boolean}\u003e;\n    Post: WithBody\u003c{username: string, password: string}, {result: boolean, token?: string, error?: string}\u003e;\n    Delete: WithNone\u003cvoid\u003e;\n}\n```\n\nNext, we can start typing out the \"todos\" route. First thing to do is to type out a Todo.\n\ntodo.ts\n```typescript\nexport interface ITodo {\n    id: number;\n    date: string;\n    title: string;\n    completed: boolean;\n}\n```\n\nAfter we have the Todo interface, we will need to type out the route.\n\ntodos-route.ts\n```typescript\nimport {WithBody, WithQuery} from \"type-rest\";\nimport {ITodo} from \"./todo\";\n\nexport interface ITodosRoute {\n    Get: WithQuery\u003c{page: number, limit?: number}, ITodo[]\u003e;\n    Post: WithBody\u003cPick\u003cITodo, Exclude\u003ckeyof ITodo, \"id\" | \"completed\"\u003e\u003e, ITodo\u003e;\n}\n```\n\nHere we see an advanced type. Basically what the `Pick\u003cTodo, Exclude\u003ckeyof Todo, \"id\" | \"completed\"\u003e\u003e`\nmeans is the all the keys of `Todo` except \"id\" and \"completed\".\n\nBut we are missing the routes which work on a single todo.\nLets type that route out separately first.\n\ntodo-route.ts\n```typescript\nimport {WithBody, WithNone} from \"type-rest\";\nimport {ITodo} from \"./todo\";\n\nexport interface ITodoRoute {\n    Get: WithNone\u003cITodo\u003e;\n    Patch: WithBody\u003cPartial\u003cITodo\u003e, ITodo\u003e;\n    Delete: WithNone\u003cvoid\u003e;\n}\n```\n\nNow that we have that route, we can modify our \"TodosRoute\" to include \nit. Seeing as the route is variable to the id of the Todo, we can use\nthe \"index\" property in typescript.\n\ntodos-route.ts\n```typescript\nimport {WithBody, WithQuery} from \"type-rest\";\nimport {ITodo} from \"./todo\";\nimport {ITodoRoute} from \"./todo-route\";\n\nexport interface ITodosRoute {\n    Get: WithQuery\u003c{page: number, limit?: number}, ITodo[]\u003e;\n    Post: WithBody\u003cPick\u003cITodo, Exclude\u003ckeyof ITodo, \"id\" | \"completed\"\u003e\u003e, ITodo\u003e;\n    [todoId: number]: ITodoRoute;\n}\n```\n\nNow, all of our routes are defined. We just need to bring it all \ntogether into a single \"root\" route which defines the entire API.\n\nroutes.ts\n```typescript\nimport {IAuthenticationRoute} from \"./authentication-route\";\nimport {ITodosRoute} from \"./todos-route\";\n\nexport interface IAwesomeApiRoutes {\n    authentication: IAuthenticationRoute;\n    todos: ITodosRoute;\n}\n```\n\n### Creating the instance\n\nWe have the entire API typed out, and want to be actually use it in\nour application. This is very easy:\n\nawesome-api.ts\n```typescript\nimport {typeRest} from \"type-rest\";\nimport {IAwesomeApiRoutes} from \"./routes\";\n\nexport const AwesomeApi = typeRest\u003cIAwesomeApiRoutes\u003e(\"https://awesome-app/api/v1/\");\n```\n\nNow, anywhere else in your app, you can use the \"AwesomeApi\" to make \nrequests to your api!\n\n## Hooks\n\nBuilt into type rest is the ability to hook into requests to modify\nthe api instance or trigger side effects. The primary purpose of the\nhooks is to handle authentication tokens, headers, etc. Many API's \nrequire a token to be sent with every request. This token can either \nbe given to the developer, or returned as part of an authentication request.\n\nIn the above example, we can see that the authentication result returns\na token. If we were required to pass this token in all future requests,\nthe user of the API would need to modify the api params.\n\n```typescript\nimport {AwesomeApi} from \"./awesome-api.ts\"\n\nconst result = AwesomeApi.authentication.Post({username: \"user\", password: \"pass\"});\n\nif (result.valid) {\n    AwesomeApi._options.params.headers[\"auth-token\"] = result.token;\n}\n```\n\nIf you as the API developer wanted to automate this for the\nconsumers of your API, you can use the hooks system.\n\nhooks.ts\n```typescript\nimport {IHook} from \"type-rest\";\n\nexport const loginHook: IHook = {\n    hook: (ev) =\u003e {\n        if (!ev.response.result) { return Promise.reject(ev.response.error); }\n        ev.instance._options.params.headers[\"auth-token\"] = ev.response.token;\n    },\n    method: \"POST\",\n    route: \"/authentication/\",\n    type: \"post\",\n};\n\nexport const logoutHook: IHook = {\n    hook: (ev) =\u003e {\n        delete ev.instance._options.params.headers[\"auth-token\"];\n    },\n    method: \"DELETE\",\n    route: \"/authentication/\",\n    type: \"post\",\n\n};\n```\n\nawesome-api-with-hooks.ts\n```typescript\nimport {typeRest} from \"type-rest\";\nimport {loginHook, logoutHook} from \"./hooks\";\nimport {IAwesomeApiRoutes} from \"./routes\";\n\nexport const AwesomeApi = typeRest\u003cIAwesomeApiRoutes\u003e(\"https://awesome-app/api/v1/\", {\n    hooks: [loginHook, logoutHook],\n});\n```\n\nIn the above example, you can see two separate hooks, a login hook and\na logout hook. The login hook will only be executed on a \"POST\" call to\nthe /authentication/ route. the logout hook will only be executed on a\n\"DELETE\" call the /authentication and will remove the token.\n\n## Adding hooks at runtime\n\nIf you have an instance of Type Rest, and want to add a hook, you can\nuse the `_addHook` method. From the examples above:\n\n```typescript\nimport {typeRest} from \"type-rest\";\nimport {loginHook, logoutHook} from \"./hooks\";\nimport {IAwesomeApiRoutes} from \"./routes\";\n\nexport const AwesomeApi = typeRest\u003cIAwesomeApiRoutes\u003e(\"https://awesome-app/api/v1/\");\n\nAwesomeApi._addHook(loginHook);\nAwesomeApi._addHook(logoutHook);\n```\n\nImportant: If `_addHook` is called from a sub-path, the hook will only apply\nto the route and its child-routes, regardless of the \"path\" property.\n\n## License\n\nCopyright 2019 Kevin Gravier\n\nPermission is hereby granted, free of charge, to any person obtaining a\ncopy of this software and associated documentation files \n(the \"Software\"), to deal in the Software without restriction, \nincluding without limitation the rights to use, copy, modify, merge, \npublish, distribute, sublicense, and/or sell copies of the Software, and \nto permit persons to whom the Software is furnished to do so, subject \nto the following conditions:\n\nThe above copyright notice and this permission notice shall be included\nin all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\nOR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\nIN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY\nCLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,\nTORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\nSOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmrkmg%2Ftype-rest","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmrkmg%2Ftype-rest","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmrkmg%2Ftype-rest/lists"}