{"_id":"@cakery/cake-rpc","_rev":"1-7d4a6991d4730d3d406fc315f6ef20b8","name":"@cakery/cake-rpc","dist-tags":{"latest":"1.0.0"},"versions":{"1.0.0":{"name":"@cakery/cake-rpc","version":"1.0.0","description":"🍰 fully typed rest library for your express & browser apps","main":"./dist/src","repository":{"type":"git","url":"git+ssh://git@github.com/illberoy/cake-rpc.git"},"author":{"name":"Roy Sommer"},"license":"MIT","private":false,"scripts":{"clean":"rm -rf dist","build":"tsc","test":"jest","release":"npx release-it","prerelease":"yarn clean && yarn build"},"bin":{"create-cake-package":"dist/src/generator.js"},"publishConfig":{"access":"public"},"peerDependencies":{"express":"^4.16.0"},"devDependencies":{"@types/chance":"^1.1.0","@types/cors":"^2.8.8","@types/execa":"^2.0.0","@types/express":"^4.17.9","@types/fs-extra":"^9.0.4","@types/jest":"^26.0.15","@types/lodash.template":"^4.5.0","@types/nock":"^11.1.0","@types/node":"^14.14.7","@types/prompts":"^2.0.9","@types/puppeteer":"^5.4.0","chance":"^1.1.7","cors":"^2.8.5","eslint":"^7.13.0","eslint-config-typescript-prettier":"github:illberoy/eslint-typescript-prettier","express":"^4.17.1","jest":"^26.6.3","nock":"^13.0.5","parcel-bundler":"^1.12.4","puppeteer":"^5.5.0","release-it":"^14.2.1","ts-jest":"^26.4.4","ts-node":"^9.0.0","twobees":"^1.1.1","typescript":"^4.0.5","wait-port":"^0.2.9","wix-eventually":"^2.3.0"},"dependencies":{"axios":"^0.21.0","execa":"^4.1.0","fs-extra":"^9.0.1","globby":"^11.0.1","lodash.template":"^4.5.0","prompts":"^2.4.0","tslib":"^2.0.3","zod":"^1.11.10"},"jest":{"preset":"ts-jest","testEnvironment":"node","testMatch":["<rootDir>/test/**/*.spec.ts"]},"gitHead":"68b9061d570e4f55449aaebabb747e6b7cb09259","bugs":{"url":"https://github.com/illberoy/cake-rpc/issues"},"homepage":"https://github.com/illberoy/cake-rpc#readme","_id":"@cakery/cake-rpc@1.0.0","_nodeVersion":"14.5.0","_npmVersion":"6.14.5","dist":{"integrity":"sha512-7yuVQPK/6Dxk3f5sChKguqTlRYm/1tCoHKCPk2oZwVnU0eghWGKRzhKawxKtlC9LWu2uVL9x3a8LkvoghK1DRQ==","shasum":"ebc1e3307d97580fa5fb7de287f9c6a0cf8be950","tarball":"https://registry.npmjs.org/@cakery/cake-rpc/-/cake-rpc-1.0.0.tgz","fileCount":25,"unpackedSize":45479,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJft8usCRA9TVsSAnZWagAAz4kP/3pE5u6JOO6ov6tk8A88\n1g7xzOZwrRUFeV+C4ry9kwsLXqXK4pDhsAs5JBrQyFd4TRa+3eil+lSl8o2s\nHR8HM6R+aoXVfXXx92iE2WXz7aXssGzLpd0hzckLYgCXNXZjYeHm5SR/cmYO\nKgVHC2ykSJc2HOZRZRneYdH+yKnMvcxTU7tW4ptvTYV42sGt/+NVi8piyPFK\nMwvkg6uZOZ/itfxpRbUnXcti2fheTaJc73rapuWDtOHbQuMcT8IlmolPoCA4\n4MXULMCKrwwPxgZ8VptqgVRgWzxTh24RCW0RKavb+ZpjQ9xevqgSS2GGjXKL\nDzdYu25tU4DmPQMhFqmPzhBavQ9Uck3RcZSQxxg68jKP23A10qkhPuIjCyIk\n7gbB9nX51FJMN8Zy86/mx+8rgyKI3DytgQdK9YLSyP8LAjK5TK9WHczkJAKI\nWu4FHUTYtr8RtXHKrGLkEG7xS7Ef3mA3Ra4T8hYyhi/m8ibmi8qpwBItNR+l\nqM93epgZmlsXu8LH1O9VxiEENZiYXrAfV0IyNCGopkWJk+9KIsFaR89oRT4x\n17FnAepHn1TM2DlSPwOVHUdrOGF7yuCeWPzLnZTXO11oN0kPzMfkh/Zt35un\nbxGadC5XGUjrtcxRo3wTkslakMjkzV6cqPWi646VUEMBSU5LYXo0RJgrPCEA\n2U5Q\r\n=asxv\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIBvHwoB8md20pZ3bF6UgoehDfw1viz3teY8nYnMN44e8AiAILW/XnwBcoeStEljmtYOcjkgbnEBK32eIX6ls1AJwXw=="}]},"_npmUser":{"name":"roysom","email":"roy@sommer.co.il"},"directories":{},"maintainers":[{"name":"roysom","email":"roy@sommer.co.il"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/cake-rpc_1.0.0_1605880747676_0.5096422080086835"},"_hasShrinkwrap":false}},"time":{"created":"2020-11-20T13:59:07.358Z","1.0.0":"2020-11-20T13:59:07.829Z","modified":"2022-04-04T21:31:31.675Z"},"maintainers":[{"name":"roysom","email":"roy@sommer.co.il"}],"description":"🍰 fully typed rest library for your express & browser apps","homepage":"https://github.com/illberoy/cake-rpc#readme","repository":{"type":"git","url":"git+ssh://git@github.com/illberoy/cake-rpc.git"},"author":{"name":"Roy Sommer"},"bugs":{"url":"https://github.com/illberoy/cake-rpc/issues"},"license":"MIT","readme":"<p align=\"center\">\n  <img src=\"logo.svg\" width=\"100\" height=\"100\" />\n</p>\n\n<h1 align=\"center\">\n  cake-rpc\n</h1>\n\n<p align=\"center\">\n<a href=\"https://github.com/illBeRoy/cake-rpc/actions?query=workflow%3A%22Node.js+CI%22\" target=\"_blank\">\n  <img src=\"https://img.shields.io/github/workflow/status/illBeRoy/cake-rpc/Node.js%20CI/master?style=flat-square\" />\n</a>\n<a href=\"https://npmjs.com/package/@cakery/cake-rpc\" target=\"_blank\">\n  <img src=\"https://img.shields.io/npm/v/@cakery/cake-rpc?style=flat-square\" />\n</a>\n</p>\n\n**cake-rpc** is a lightweight library for defining, implementing and consuming restful APIs using a fully structured interface with both static & runtime type safety. With **cake-rpc** you can easily:\n- Define restful services using an easy js\\ts lib 🎬\n- Implement them in your express server (plugs right in as a router, no config needed!) 🚂\n- Use from your client as if it was a class you imported 🌍\n- Keeps you type safe: full typescript support, as well as runtime type validation 🎖\n\n## Getting Started in 3 Steps\n### Step 1: Define your service\nStart by installing **cake-rpc** in your project:\n```sh\nnpm install @cakery/cake-rpc\n```\n\nNow create a new file called `service.ts`, and define a service using `createService`:\n```ts\nimport { createService } from '@cakery/cake-rpc';\n\nexport const echoService = createService({\n  echo: {\n    path: '/echo',\n    method: 'POST',\n    request: (z) => ({\n      reqText: z.string().max(20),\n    }),\n    response: (z) => ({\n      resText: z.string().max(20),\n    }),\n  },\n});\n```\n\nWe declared a service (called `echoService`), with one method called: `echo`:\n* The path (url) to the method is `/echo`\n* The http method to be used is `POST`\n* The request body should contain a single string called `reqText` with up to 20 chars (more about schemas in the [relevant chapter](#creating-service-schemas))\n* The response body will contain a single string called `reqText` with up to 20 chars\n\n### Step 2: Implement using your Express server\n**cake-rpc** is designed to be plugged into your express server with zero boilerplate or configuration. You don't need to change anything, just implement an express router!\n\nGo to your express app and add the following:\n```ts\nimport { createRouter } from '@cakery/cake-rpc/express';\nimport { echoService } from './service';\n\nexport const echoServiceRouter = createRouter(echoService, {\n  echo: (req, res) => {\n    res.send({ resText: req.body.reqText });\n  },\n});\n\napp.use('/api', echoServiceRouter);\n```\n\nWhat we did is to create an express router from our service, where we implemented the `echo` method:\n- The echo implementation is a simple express route\n- No need to validate input! The request body is automatically checked for your before your route is even run\n- If you are using typescript: `req.body` is fully typed for your convenience!\n- If you are using typescript: `res.json` and `res.send` are fully typed for your convenience!\n- The entire router is nested under `/api`. In theory, you can plug in as many **cake-rpc** service you'd like, and simply nest them under different routes!\n\nWe then took the router and utilized it by our express app!\n\n### Step 3: Use your service from the client\nNow we want to actually use our echo service. Let's go to our client's code and add the following:\n```ts\nimport { createClient } from '@cakery/cake-rpc/client';\nimport { echoService } from '../service/echo';\n\nconst echoClient = createClient(echoService, '/api');\n\nechoClient.echo({ reqText: 'hello, world!' })\n  .then(res => console.log(res.data.resText));\n```\n\nHere, we simply created an instance of our echo service's client, and used it to echo `hello, world`:\n- We created the client with `echoService` as the service, and `/api` as base url. If your client is served from the same server you used in step 2, you can keep it that way. Otherwise, you need to input full url (e.g. `http://localhost:1234/api`). If you are serving your app from a different server, don't forget [cors policy](https://npmjs.com/package/cors) as well!\n- The created client has all the methods from `echoService`. We can invoke any of them with the relevant body!\n- The method resolves to an [axios](https://github.com/axios/axios) response, where we have `status` for http status code, and `data` for the body\n- If you are using typescript: `.echo({...})` is fully typed for your convenience!\n- If you are using typescript: `res.data` is fully typed for your convenience!\n\n**Congratulations!** You have successfully implemented your first cake-rpc service. You can go ahead and give it a spin in your existing express applications - no boilerplate, configurations or strings attached!\n\n## Usage\n### Installing cake-rpc\nYou can start by installing **cake-rpc** using your favorite package manager:\n\nnpm:\n```sh\nnpm install cake-rpc\n```\n\nOr yarn:\n```sh\nyarn add cake-rpc\n```\n\nThe **cake-rpc** actually has three entrypoints:\n1. The first one is the default `@cakery/cake-rpc` import, which provides you with the service factory\n2. The second one is `@cakery/cake-rpc/express` - this is where you import the server-side tools into your express application\n3. The third one is `@cakery/cake-rpc/client` - this is where you import the client factory from, and should be used by your client application\n\n### Creating Service Schemas\nAt its core, **cake-rpc** operates with *services*. You can think of them as interfaces that define all the api methods that you want to expose.\n\nIn order to create a new service, you need to import the `createService` factory method from the main entrypoint:\n```ts\nimport { createService } from '@cakery/cake-rpc';\n```\n\nYou can then use it to create a service schema:\n```ts\nconst guestListService = createService({\n  listGuests: { ... },\n  addGuest: { ... },\n  removeGuest: { ... }\n})\n```\n\nIn this example, we created a service with three methods: `listGuests`, `addGuest` and `removeGuest`.\n\nEvery method has four parameters to describe:\n1. The path (url) to it\n2. The http method to use\n3. The type of the request body (or query string, for GET \\ DELETE requests)\n4. The type of the response body\n\nTake a look at the following example for addGuest:\n```ts\nconst guestListService = createService({\n  ...\n  addGuest: {\n    path: '/guests',\n    method: 'POST',\n    request: (z) => ({\n      name: z.string().min(3).max(20),\n      age: z.number().int().positive(),\n      plusOne: z.boolean(),\n    }),\n    response: (z) => ({\n      success: z.boolean(),\n    }),\n  },\n  ...\n})\n```\nThe request is a `POST` to `/guests`, and should denote name, age, and plusOne fields. The response denotes success using a boolean value.\n\n**The schema builder** for the request and the response actually uses [zod](https://github.com/vriad/zod), a light and powerful schema declaration library.\n\nOnce we're done defining our service, it's time to implement it!\n\n### Implementing Service in Express\n**cake-rpc** wants to take as little of your attention as possible, unlike other terrific fullstack frameworks, such as [next.js](https://nextjs.org/) or [meteor](https://github.com/meteor/meteor). As a matter of fact, we consider **cake-rpc** a library, and not a framework.\n\nAs such, **cake-rpc** plugs into your `express` application as a [router](https://expressjs.com/en/api.html#router).\n\nYou can start by importing the router factory:\n```ts\nimport { createRouter } from '@cakery/cake-rpc/express';\n```\n\nNow, create an express router for your service:\n```ts\nconst guestListRouter = createRouter(guestListService, {\n  listGuests: (req, res) => { ... },\n  addGuest: (req, res) => { ... },\n  removeGuest: (req, res) => { ... },\n})\n```\n\nFor every method, you simply have to define an express handler that would handle it - as simple as that. All the requests made to your server are validated **before** they are passed to the handler, so you can rest assured that if a request made it to your code, it was already validated!\n\nFew other neat quality of life features when implementing your router are:\n1. The router supports async functions, including error handling\n2. The request is fully typed - `req.body` will match the schema that you defined for the request\n2. The response is fully typed - `req.json` and `req.data` will hint at the schema that you defined for the response\n\nFinally, attach the router to your express app:\n```ts\napp.use(guestListRouter);\n```\n\nAs we said, the router is a simple `express` router, so you can use it as any other middleware, for example:\n```ts\napp.use('/api/v1', [cors(), authenticateUser(), guestListRouter]);\n```\n\n### Initializing your Client\nNow that you've implemented your server, you can initialize your client.\n\nStart by importing the `createClient` factory:\n```ts\nimport { createClient } from '@cakery/cake-rpc/client';\n```\n\nThe next thing to do is to create your client:\n```ts\nconst guestListClient = createClient(guestListService, '/');\n```\n\nThe `createClient` factory returns a full client that is based on the famous [axios](https://github.com/axios/axios) library. Think of it as a typed axios client: you can configure it as you would axios, and the response format is similar to axios, but instead of `get` `post` `put` (and other) methods, you get the service's methods (in our case, `listGuests`, `addGuest` and `deleteGuest`).\n\nThe `createClient` function receives up to three parameters:\n1. The service definition (required) - the object we created using `createService`\n2. The base url (required) - the base url under which the router can be found (can be relative if your app is served from the same server where the api can be found, otherwise it should be a full url)\n3. Axios configuration (optional) - a configuration object that will be passed to the underlying axios client.\n\nWhen invoking a method, all you need to do is pass the request payload:\n```ts\nguestListClient.addGuest({ name: 'Roy', age: 28, plusOne: true });\n```\n\nIf you are using typescript, `addGuest` will be fully typed and hint at the correct request structure. In addition, all requests are validated on the client before being sent to the server for your convenience.\n\nAdditionally, you can pass an Axios config to the request as well, e.g.:\n```ts\nguestListClient.addGuest({ name: 'Roy', age: 28, plusOne: true }, { headers: { 'x-my-identity': 'super admin' } });\n```\n\nℹ️ Regarding your bundle: if you are bundling your apps using webpack or parcel, fear not! **cake-rpc** only brings the essential code for your client-side application, since you import it from `@cakery/cake-rpc/client`. Nothing related to `express` will be imported.\n\n### Making API Changes (Backwards Compatibility)\nAPIs are dynamic beings - we change and we add to them all the time. Question being asked - how does **cake-rpc** handle API changes? Let's divide it into two parts: requests and responses.\n\n#### Requests\nIf you are adding fields to your request schemas, you cant take one of two approaches:\n\n1. Make it optional. That way, you can provide backwards compatibility for older clients:\n```diff\naddGuest: {\n  request: (z) => ({\n    name: z.string().min(3).max(20),\n    age: z.number().int().positive(),\n    plusOne: z.boolean(),\n+   hasARide: z.boolean().optional()\n  }),\n}\n```\n\n2. Make the new field required. In that case, all clients using the older schema will be rejected by your new server.\n\nOne way or another, if you are making breaking changes to your api, we suggest that you follow [api versioning best practices](https://restfulapi.net/versioning). \n\n#### Responses\nBy default, you can always add new fields to the response. The **cake-rpc** client takes that into account, and simply ignores fields it is not familiar with.\n\nThat said, if you want to remove fields from the response, it will most definitely break the client, causing it to throw accordingly and tell you to check your compatibility with the server schema. Therefore, if you want to remove fields, consider this a breaking api change, and preferably follow through with api versioning.\n\n### Working in Multi-Package Workspaces\nOf course, many projects tend to put the server and the client in separate packages. Are you using [lerna](https://github.com/lerna/lerna) or [yarn workspaces](https://classic.yarnpkg.com/en/docs/workspaces/)? We got you covered!\n\nYou can basically create your services as packages within your workspace \\ monorepo, and consume them both in your server and client projects:\n\n#### The Easy Way\nCake comes with a straight-forward tool called `create-cake-package`. When run, it will generate a package with all the required setup, so you can go ahead and start implementing your service immediately. Use it by running the following command:\n\n```sh\nnpx create-cake-package\n```\n\n#### The Custom Way\nAlternatively, you can create such packages on your on, in a way that matches your workflow. The only dependency for said packages would be **cake-rpc** itself.\n\nYou can take a look at a minimal implementation [here](assets/templates/service-package-ts).\n\n## FAQ\n### Why cake-rpc?\nI've been working on fullstack web projects for a long time now, and no matter where I worked and on which system, there was always a single most important concern: how do we manage integrations between multiple services, servers and clients and not lose our minds?\n\nIn one of my workplaces, I attempted to solve this exact problem, and came up with a system that I am proud to say is used by the entire company (and even our dev sdk) to this day. From it, I learned the following:\n1. Most people want to write as little boilerplate as possible\n2. But they almost never want to commit their entire codebase to a full framework\n\nThat is, people want things to work fast and simple, and not have to wrap their entire program around a specific framework that wraps around and abstracts away their simple express \\ react apps.\n\nThat's why I came up with **cake-rpc** - inspired by my experience with said libraries, I wanted to create a library that is as simple to plug into as an express router, and which you don't have to commit to - if you don't feel like using it anymore, just uninstall it and you're done. In short: comprehensive, but non-intrusive.\n\n### What if I'm using Koa? Or Fastify? Or something else?\nAs you can see, the `createRouter` method from express is imported from `@cakery/cake-rpc/express`. In the future, and according to popular demand, adapters for `koa`, `fastify` and others might be added as well.\n\n### What if I don't want Axios?\nI went with `axios` first as it is my own requests library of choice. I do intend to implement a client base on `fetch` as well, so if you don't want to include `axios` in your bundle and prefer to use `fetch` - you will be able to pretty soon!\n\n### What about other languages?\nIn order to keep it simple, **cake-rpc** is currently directed at a full js\\ts stack. In the future, if demand arises, it is not out of the question to port cake to other languages as well!\n\n## Contribution\nI've yet to set up a contribution guide per se, but feel free to open issues and pull requests! I promise to try and be attentive to your requests and contributions.\n\n**Tried it? Liked it? Be a star and give us a 🌟!**\n\n> logo by [freepik](https://www.flaticon.com/authors/freepik) from [flaticon.com](https://www.flaticon.com)\n","readmeFilename":"README.md"}