{"id":13493429,"url":"https://github.com/brillout/wildcard-api","last_synced_at":"2025-04-06T22:09:18.298Z","repository":{"id":46689391,"uuid":"156608648","full_name":"brillout/wildcard-api","owner":"brillout","description":"Functions as API.","archived":false,"fork":false,"pushed_at":"2022-02-17T15:55:01.000Z","size":4298,"stargazers_count":368,"open_issues_count":13,"forks_count":14,"subscribers_count":5,"default_branch":"master","last_synced_at":"2025-03-30T21:06:57.190Z","etag":null,"topics":["api","javascript","node","nodejs","rpc"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","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/brillout.png","metadata":{"files":{"readme":"readme.md","changelog":null,"contributing":null,"funding":null,"license":"license.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-11-07T21:11:22.000Z","updated_at":"2024-11-05T11:41:01.000Z","dependencies_parsed_at":"2022-08-03T05:15:39.621Z","dependency_job_id":null,"html_url":"https://github.com/brillout/wildcard-api","commit_stats":null,"previous_names":["reframejs/wildcard-api"],"tags_count":13,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/brillout%2Fwildcard-api","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/brillout%2Fwildcard-api/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/brillout%2Fwildcard-api/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/brillout%2Fwildcard-api/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/brillout","download_url":"https://codeload.github.com/brillout/wildcard-api/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247557767,"owners_count":20958047,"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","javascript","node","nodejs","rpc"],"created_at":"2024-07-31T19:01:15.135Z","updated_at":"2025-04-06T22:09:18.274Z","avatar_url":"https://github.com/brillout.png","language":"JavaScript","readme":"\u003c!---\n\n\n\n\n\n\n    WARNING, READ THIS.\n    This is a computed file. Do not edit.\n    Instead, edit `/docs/readme.template.md` and run `npm run docs` (or `yarn docs`).\n\n\n\n\n\n\n\n\n\n\n\n\n    WARNING, READ THIS.\n    This is a computed file. Do not edit.\n    Instead, edit `/docs/readme.template.md` and run `npm run docs` (or `yarn docs`).\n\n\n\n\n\n\n\n\n\n\n\n\n    WARNING, READ THIS.\n    This is a computed file. Do not edit.\n    Instead, edit `/docs/readme.template.md` and run `npm run docs` (or `yarn docs`).\n\n\n\n\n\n\n\n\n\n\n\n\n    WARNING, READ THIS.\n    This is a computed file. Do not edit.\n    Instead, edit `/docs/readme.template.md` and run `npm run docs` (or `yarn docs`).\n\n\n\n\n\n\n\n\n\n\n\n\n    WARNING, READ THIS.\n    This is a computed file. Do not edit.\n    Instead, edit `/docs/readme.template.md` and run `npm run docs` (or `yarn docs`).\n\n\n\n\n\n\n--\u003e\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"/../../#readme\"\u003e\n    \u003cimg src=\"/docs/images/logo-title.svg\" height=\"105\" alt=\"Wildcard API\"/\u003e\n  \u003c/a\u003e\n\u003c/p\u003e\n\n\u003cbr/\u003e\n\n\u003e ⚠️ Check out [Telefunc](https://telefunc.com/), the official successor of Wildcard.\n\n\u003cbr/\u003e \u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026#8226;\u0026nbsp;\n[What is Wildcard](#what-is-wildcard)\n\u003cbr/\u003e \u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026#8226;\u0026nbsp;\n[Wildcard compared to REST and GraphQL](#wildcard-compared-to-REST-and-GraphQL)\n\u003cbr/\u003e \u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026#8226;\u0026nbsp;\n[Learning Material](#learning-material)\n\u003cbr/\u003e \u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026#8226;\u0026nbsp;\nUsage\n\u003cbr/\u003e \u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026#8226;\u0026nbsp;\n[Getting Started](#getting-started)\n\u003cbr/\u003e\n\u003csub\u003e\n\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\nBasics\n\u003c/sub\u003e\n\u003cbr/\u003e \u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026#8226;\u0026nbsp;\n[Authentication](#authentication)\n\u003cbr/\u003e \u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026#8226;\u0026nbsp;\n[Permissions](#permissions)\n\u003cbr/\u003e \u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026#8226;\u0026nbsp;\n[Validation](#validation)\n\u003cbr/\u003e \u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026#8226;\u0026nbsp;\n[Error Handling](#error-handling)\n\u003cbr/\u003e\n\u003csub\u003e\n\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\nMore\n\u003c/sub\u003e\n\u003cbr/\u003e \u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026#8226;\u0026nbsp;\n[TypeScript](#typescript)\n\u003cbr/\u003e \u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026#8226;\u0026nbsp;\n[API Documentation](#api-documentation)\n\u003cbr/\u003e \u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026#8226;\u0026nbsp;\n[Caching](#caching)\n\u003cbr/\u003e \u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026#8226;\u0026nbsp;\n[SSR](#ssr)\n\u003cbr/\u003e \u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026#8226;\u0026nbsp;\n[Options](#options)\n\n\u003cbr/\u003e\n\n## What is Wildcard\n\nWildcard is a JavaScript library to create an API between your Node.js backend and your browser frontend.\n\nWith Wildcard,\ncreating an API endpoint is as easy as creating a JavaScript function.\n\n~~~js\n// Node.js server\n\nconst { server } = require('@wildcard-api/server');\n\n// We define a `hello` function on the server\nserver.hello = function(name) {\n  return {message: 'Welcome '+name};\n};\n~~~\n\n~~~js\n// Browser\n\nimport { server } from '@wildcard-api/client';\n\n(async () =\u003e {\n  // Wildcard makes our `hello` function available in the browser\n  const {message} = await server.hello('Elisabeth');\n  console.log(message); // Prints `Welcome Elisabeth`\n})();\n~~~\n\nThat's all Wildcard does:\nit makes functions,\nthat are defined on your Node.js server,\ncallable in the browser.\nNothing more, nothing less.\n\nTo retrieve and mutate data, you can direclty use SQL or an ORM.\n\n~~~js\n// Node.js server\n\nconst { server } = require('@wildcard-api/server');\nconst Todo = require('./path/to/your/data/models/Todo');\n\nserver.createTodoItem = async function(text) {\n  if( !this.user ) {\n    // The user is not logged-in. We abort.\n    // With Wildcard, you define permissions programmatically\n    // which we talk more about in the \"Permissions\" section.\n    return;\n  }\n\n  // With an ORM:\n  const newTodo = new Todo({text, authorId: this.user.id});\n  await newTodo.save();\n\n  /* With SQL:\n  const db = require('your-favorite-sql-query-builder');\n  const [newTodo] = await db.query(\n    \"INSERT INTO todos VALUES (:text, :authorId);\",\n    {text, authorId: this.user.id}\n  );\n  */\n\n  return newTodo;\n};\n~~~\n\nWildcard is used in production at many companies,\nevery release is assailed against a heavy suite of automated tests,\nand issues are fixed promptly.\nIt is financed with the [Lsos](https://github.com/reframejs/wildcard-api/issues/56).\n\n\u0026nbsp;\n\n\u003cp align=\"center\"\u003e\n  :sparkles:\n  \u003cb\u003eEasy Setup\u003c/b\u003e\n  \u003cimg src=\"/docs/images/blank.svg\" height=\"1\" width=\"85\" align=\"middle\"/\u003e\n  :shield:\n  \u003cb\u003eSimple Permissions\u003c/b\u003e\n  \u003cimg src=\"/docs/images/blank.svg\" height=\"1\" width=\"42\" align=\"middle\"/\u003e\n  :rotating_light:\n  \u003cb\u003eSimple Error Handling\u003c/b\u003e\n  \u003cimg src=\"/docs/images/blank.svg\" height=\"1\" width=\"1\" align=\"middle\"/\u003e\n  \u003cbr/\u003e\n  :detective:\n  \u003cb\u003eDev Tools\u003c/b\u003e\n  \u003cimg src=\"/docs/images/blank.svg\" height=\"1\" width=\"96\" align=\"middle\"/\u003e\n  :microscope:\n  \u003cb\u003eTypeScript Support\u003c/b\u003e\n  \u003cimg src=\"/docs/images/blank.svg\" height=\"1\" width=\"47\" align=\"middle\"/\u003e\n  :memo:\n  \u003cb\u003eSSR Support\u003c/b\u003e\n  \u003cimg src=\"/docs/images/blank.svg\" height=\"1\" width=\"71\" align=\"middle\"/\u003e\n  \u003cbr/\u003e\n  :zap:\n  \u003cb\u003eAutomatic Caching\u003c/b\u003e\n  \u003cimg src=\"/docs/images/blank.svg\" height=\"1\" width=\"455\" align=\"middle\"/\u003e\n\u003c/p\u003e\n\n\u0026nbsp;\n\n\u003e The seamless \"drop in and use\" nature of Wildcard has enabled Vibescout to accelerate the development of new features, it enables us to quickly prototype new ideas and build out internal dashboards with ease (without the unneeded verbosity of things like GraphQL). The barrier between our server and client is almost nonexistent now- it's really just a function!\n\u003cp align=\"right\"\u003e\nPaul Myburgh, CTO of Vibescout \u003ca href=\"https://github.com/reframejs/wildcard-api/issues/22#issuecomment-566983911\"\u003e(ref)\u003c/a\u003e\n\u003c/p\u003e\n\n\u0026nbsp;\n\n\u003e We are a web shop and decided to try Wildcard with one of our projects. We were delighted: not only made Wildcard our front-end development simpler and faster but it also allowed us to easily implement features that were previously difficult to implement with the rigid structure of REST and GraphQL. We now use it for all our new Node.js projects and we couldn't be happier. The cherry on the cake: it now supports TypeScript which, for us, makes Wildcard a no-brainer.\n\u003cp align=\"right\"\u003e\nNiels Litt \u003ca href=\"https://github.com/reframejs/wildcard-api/issues/22#issuecomment-568246660\"\u003e(ref)\u003c/a\u003e\n\u003c/p\u003e\n\n\u0026nbsp;\n\n## Wildcard compared to REST and GraphQL\n\nREST and GraphQL are well-suited tools to create an API that is meant to be used by third-party developers.\nFacebook's API, for example, is used by ~200k third parties.\nIt is no surprise that Facebook is using (and invented) GraphQL;\nit enables third-party developers\nto extensively access Facebook's social graph\nallowing them to build all kinds of applications.\nFor an API used by many third parties with many diverse uses cases, GraphQL is the right tool.\n\nHowever,\nif you want to create a backend API that is meant to be consumed only by your frontend,\nthen you don't need REST nor GraphQL \u0026mdash; [RPC](/docs/what-is-rpc.md#what-is-rpc), such as Wildcard, is enough.\n\nFor a large app, you may still want the structure that comes with a RESTful/GraphQL API.\nBut this typically applies only for large companies that develop apps with a large number of developers.\n\"Premature optimization is the root of all evil\";\nstart with\n[RPC as default](/docs/blog/rpc-as-default.md#rpc-as-default)\nand later switch to [REST or GraphQL](/docs/blog/rest-or-graphql.md#rest-or-graphql-a-simple-answer)\nwhen (and only if!) the need arises.\n\nIn a nuthsell:\n\u003cbr/\u003e \u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026#8226;\u0026nbsp;\nIs your API meant to be used by third parties? Use REST or GraphQL.\n\u003cbr/\u003e \u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026#8226;\u0026nbsp;\nIs your API meant to be used by yourself? Use RPC.\n\n\u0026nbsp;\n\n## Getting Started\n\n1. Install Wildcard.\n\n   With Express:\n   ~~~js\n   const express = require('express');\n   // npm install @wildcard-api/server\n   const { wildcard } = require('@wildcard-api/server/express');\n\n   const app = express();\n\n   // We install the Wildcard middleware\n   app.use(wildcard(setContext));\n\n   // `setContext` is called on every API request. It defines the `context` object.\n   // `req` is Express' request object\n   async function setContext(req) {\n     const context = {};\n     // Authentication middlewares usually make user information available at `req.user`.\n     context.user = req.user;\n     return context;\n   }\n   ~~~\n\n   \u003cdetails\u003e\n   \u003csummary\u003e\n   With Hapi\n   \u003c/summary\u003e\n\n   ~~~js\n   const Hapi = require('hapi');\n   // npm install @wildcard-api/server\n   const { wildcard } = require('@wildcard-api/server/hapi');\n\n   const server = Hapi.Server();\n\n   // We install the Wildcard middleware\n   await server.register(wildcard(setContext));\n\n   // `setContext` is called on every API request. It defines the `context` object.\n   // `request` is Hapi's request object\n   async function setContext(request) {\n     const context = {};\n     // Authentication plugins usually make user information\n     // available at `request.auth.credentials`.\n     context.user = request.auth.isAuthenticated ? request.auth.credentials : null;\n     return context;\n   }\n   ~~~\n   \u003c/details\u003e\n\n   \u003cdetails\u003e\n   \u003csummary\u003e\n   With Koa\n   \u003c/summary\u003e\n\n   ~~~js\n   const Koa = require('koa');\n   // npm install @wildcard-api/server\n   const { wildcard } = require('@wildcard-api/server/koa');\n\n   const app = new Koa();\n\n   // We install the Wildcard middleware\n   app.use(wildcard(setContext));\n\n   // `setContext` is called on every API request. It defines the `context` object.\n   async function setContext(ctx) {\n     const context = {};\n     // Authentication middlewares often make user information available at `ctx.state.user`.\n     context.user = ctx.state.user;\n     return context;\n   }\n   ~~~\n   \u003c/details\u003e\n\n   \u003cdetails\u003e\n   \u003csummary\u003e\n   With other server frameworks\n   \u003c/summary\u003e\n\n   Wildcard provides a `getApiHttpResponse()` function which\n   returns the HTTP response of API requests;\n   by using `getApiHttpResponse()` you can\n   integrate Wildcard with any server framework.\n   In fact, the Express/Koa/Hapi middlewares are just tiny wrappers around `getApiHttpResponse()`.\n\n   ~~~js\n   // This is generic pseudo code for how to integrate Wildcard with any server framework.\n\n   // npm install @wildcard-api/server\n   const { getApiHttpResponse } = require('@wildcard-api/server');\n\n   // A server framework usually provides a way to add a route and define an HTTP response.\n   const { addRoute, HttpResponse } = require('your-favorite-server-framework');\n\n   // Add a new route `/_wildcard_api/*` to your server\n   addRoute(\n     '/_wildcard_api/*',\n     // A server framework usually provides an object holding\n     // information about the request. We denote this object `req`.\n     async ({req}) =\u003e {\n       // The context object is available to endpoint functions as `this`.\n       const context = {\n         user: req.user, // Information about the logged-in user.\n       };\n\n       const {\n         url, // The HTTP request url (or pathname)\n         method, // The HTTP request method (`GET`, `POST`, etc.)\n         body, // The HTTP request body\n       } = req;\n\n       const responseProps = await getApiHttpResponse({url, method, body}, context);\n\n       const {body, statusCode, contentType} = responseProps;\n       const response = new HttpResponse({body, statusCode, contentType});\n       return response;\n     }\n   );\n   ~~~\n   \u003c/details\u003e\n\n2. Define an endpoint function `myFirstEndpoint` in a file called `endpoints.js`.\n\n   ~~~js\n   // Node.js server\n\n   const { server } = require('@wildcard-api/server');\n\n   server.myFirstEndpoint = async function () {\n     // The `this` object is the `context` object we defined in `setContext`.\n     console.log('The logged-in user is: ', this.user.username);\n\n     return {msg: 'Hello, from my first Wildcard endpoint'};\n   };\n   ~~~\n\n   \u003e :information_source:\n   \u003e Wildcard automatically loads files named `endpoints.js` or `*.endpoints.js`.\n\n3. Use the `@wildcard-api/client` package to remotely call `enpdoint.myFirstEndpoint` from the browser.\n\n   ~~~js\n   // Browser\n\n   // npm install @wildcard-api/client\n   import { server } from '@wildcard-api/client';\n\n   (async () =\u003e {\n     const {msg} = await server.myFirstEndpoint();\n     console.log(msg);\n   })();\n   ~~~\n\n   \u003cdetails\u003e\n   \u003csummary\u003e\n   Without bundler\n   \u003c/summary\u003e\n\n   ~~~html\n   \u003cscript crossorigin src=\"https://unpkg.com/@wildcard-api/client/wildcard-client.production.min.js\"\u003e\u003c/script\u003e\n   \u003cscript src=\"my-script-using-wildcard.js\"\u003e\u003c/script\u003e\n   ~~~\n   ~~~js\n   // my-script-using-wildcard.js\n\n   (async () =\u003e {\n     const {msg} = await wildcard.server.myFirstEndpoint();\n     console.log(msg);\n   })();\n   ~~~\n   \u003c/details\u003e\n\n\nThat's it.\n\n\n\u003cbr/\u003e\n\n\u003cp align=\"center\"\u003e\n\n\u003csup\u003e\n\u003ca href=\"https://github.com/reframejs/wildcard-api/issues/new\"\u003eOpen a GitHub ticket\u003c/a\u003e\nif you have questions or something's not clear \u0026mdash; we enjoy talking with our users.\n\u003c/sup\u003e\n\n\u003cbr/\u003e\n\n\u003csup\u003e\n\u003ca href=\"#readme\"\u003e\u003cb\u003e\u0026#8679;\u003c/b\u003e \u003cb\u003eTOP\u003c/b\u003e \u003cb\u003e\u0026#8679;\u003c/b\u003e\u003c/a\u003e\n\u003c/sup\u003e\n\n\u003c/p\u003e\n\n\u003cbr/\u003e\n\u003cbr/\u003e\n\n\n\n## Authentication\n\nUse the context object to authenticate requests. For example:\n\n~~~js\n// Node.js server\n\nconst express = require('express');\nconst { wildcard } = require('@wildcard-api/server/express');\n\nconst app = express();\n\n// We install the Wildcard middleware\napp.use(wildcard(setContext));\n\nasync function setContext(\n  // The `req` Express request object.\n    req\n  ) {\n\n  const context = {};\n\n  // Express authentication middlewares usually make information\n  // about the logged-in user available at `req.user`.\n  context.user = req.user;\n\n  // We add login and logout functions to the context object.\n  // That way we make them available to our endpoint functions.\n  context.login = req.auth.login;\n  context.logout = req.auth.logout;\n\n  return context;\n}\n~~~\n\nThe context object is available to endpoint functions as `this`.\n\n~~~js\n// Node.js server\n\nconst { server } = require('@wildcard-api/server');\n\nserver.whoAmI = async function() {\n  const {user} = this;\n  return user.name;\n};\n\nserver.login = async function(username, password) {\n  const user = await this.login(username, password);\n  return user;\n};\n\nserver.logout = async function() {\n  await this.logout();\n};\n~~~\n\nFor SSR, read [SSR \u0026 Authentication](/docs/ssr-auth.md#ssr--authentication).\n\n\n\u003cbr/\u003e\n\n\u003cp align=\"center\"\u003e\n\n\u003csup\u003e\n\u003ca href=\"https://github.com/reframejs/wildcard-api/issues/new\"\u003eOpen a GitHub ticket\u003c/a\u003e\nif you have questions or something's not clear \u0026mdash; we enjoy talking with our users.\n\u003c/sup\u003e\n\n\u003cbr/\u003e\n\n\u003csup\u003e\n\u003ca href=\"#readme\"\u003e\u003cb\u003e\u0026#8679;\u003c/b\u003e \u003cb\u003eTOP\u003c/b\u003e \u003cb\u003e\u0026#8679;\u003c/b\u003e\u003c/a\u003e\n\u003c/sup\u003e\n\n\u003c/p\u003e\n\n\u003cbr/\u003e\n\u003cbr/\u003e\n\n\n\n## Permissions\n\nWith Wildcard,\npermissions are defined programmatically.\n\n~~~js\n// Node.js server\n\nserver.deletePost = async function(){\n  // Only admins are allowed to remove a post\n  if( !user.isAdmin ) {\n    // The user is not an admin — we abort.\n    return;\n  }\n\n  // ...\n};\n~~~\n\nIt is crucial to define permissions; never do something like this:\n~~~js\n// Node.js server\n\nconst db = require('your-favorite-sql-query-builder');\n\nserver.executeQuery = async function(query) {\n  const result = await db.run(query);\n  return result;\n};\n~~~\n\nThat's a bad idea since anyone can go to your website,\nopen the browser's web dev console, and call your endpoint.\n~~~js\n// Browser\n\nconst users = await server.executeQuery('SELECT login, password FROM users;');\nusers.forEach(({login, password}) =\u003e {\n  // W00t I have all passwords ｡^‿^｡\n  console.log(login, password);\n});\n~~~\n\nInstead, you should define permissions, for example:\n~~~js\n// Node.js server\n\n// This endpoint allows a to-do item's text to be modified only by its author.\n\nserver.updateTodoText = async function(todoId, newText) {\n  // The user is not logged in — we abort.\n  if( !this.user ) return;\n\n  const todo = await db.getTodo(todoId);\n  // There is no to-do item in the database with the ID `todoId` — we abort.\n  if( !todo ) return;\n\n  // The user is not the author of the to-do item — we abort.\n  if( todo.authorId !== this.user.id ) return;\n\n  // The user is logged-in and is the author of the todo — we proceed.\n  await db.updateTodoText(todoId, newText);\n};\n~~~\n\nYou may wonder why we return `undefined` when aborting.\n\n~~~js\n// Node.js server\n\nif( !this.user ){\n  // Why do we return `undefined`?\n  // Why don't we return something like `return {error: 'Permission denied'};`?\n  return;\n}\n~~~\n\nThe reason is simple:\nwhen we develop the frontend we know what is allowed and we can\ndevelop the frontend to always call endpoints in an authorized way;\nthe `return;` sole goal are to protect our server from unsafe requests and\nthere is no need to return information.\n\nThat said, there are exceptions, for example:\n~~~js\n// When the user is not logged in, the frontend redirects the user to the login page.\n\nserver.getTodoList = async function() {\n  const isLoggedOut = !this.user;\n  if( isLoggedOut ) {\n    // Instead of returning `undefined` we return `{isNotLoggedIn: true}` so that\n    // the frontend knows that the user should be redirected to the login page.\n    return {isNotLoggedIn: true};\n  }\n  // ...\n};\n~~~\n\nIn any case,\nas long as you protect your endpoints from unsafe requests,\nyou can do whatever works for you.\n\n\n\u003cbr/\u003e\n\n\u003cp align=\"center\"\u003e\n\n\u003csup\u003e\n\u003ca href=\"https://github.com/reframejs/wildcard-api/issues/new\"\u003eOpen a GitHub ticket\u003c/a\u003e\nif you have questions or something's not clear \u0026mdash; we enjoy talking with our users.\n\u003c/sup\u003e\n\n\u003cbr/\u003e\n\n\u003csup\u003e\n\u003ca href=\"#readme\"\u003e\u003cb\u003e\u0026#8679;\u003c/b\u003e \u003cb\u003eTOP\u003c/b\u003e \u003cb\u003e\u0026#8679;\u003c/b\u003e\u003c/a\u003e\n\u003c/sup\u003e\n\n\u003c/p\u003e\n\n\u003cbr/\u003e\n\u003cbr/\u003e\n\n\n## Validation\n\nYou shouldn't throw exceptions upon validation failures,\ninstead return an object containing the validation failure reason.\n\n~~~js\n// Node.js server\n\nconst { server } = require('@wildcard-api/server');\nconst isStrongPassword = require('./path/to/isStrongPassword');\n\nserver.createAccount = async function({email, password}) {\n  if( !isStrongPassword(password) ){\n    /* Don't deliberately throw exceptions\n    throw new Error(\"Password is too weak.\");\n    */\n    // Return a value instead:\n    return {validationError: \"Password is too weak.\"};\n  }\n\n  // ..\n};\n~~~\n\n\n## Error Handling\n\nCalling an endpoint throws an error if and only if:\n- the browser couldn't connect to the server (`isConnectionError`), or\n- the endpoint threw an error or doesn't exist (`isCodeError`).\n\nThe client-side thrown error has the properties `isCodeError` and `isConnectionError`\nenabling you to handle errors with precision, for example:\n\n~~~js\n// Browser\n\nimport { server } from '@wildcard-api/client';\n\n(async () =\u003e {\n  let data, err;\n  try {\n    data = await server.getSomeData();\n  } catch(_err) {\n    err = _err;\n  }\n\n  if( err.isCodeError ){\n    // The endpoint function threw an uncaught error (there is a bug in your server code)\n    alert(\n      'Something went wrong on our side. We have been notified and we are working on a fix.' +\n      'Sorry... Please try again later.'\n    );\n  }\n  if( err.isConnectionError ){\n    // The browser couldn't connect to the server; the user is offline or the server is down.\n    alert(\"We couldn't perform your request. Please try again.\");\n  }\n\n  if( err ) {\n    return {success: false};\n  } else {\n    return {success: true, data};\n  }\n})();\n~~~\n\nYou can also use [Handli](https://github.com/brillout/handli) which will automatically and gracefully handle errors for you.\n\n~~~js\n// Browser\n\nimport 'handli'; // npm install handli\n// That's it, Wildcard will automatically use Handli.\n// Errors are now handled by Handli.\n~~~\n\n\n\u003cbr/\u003e\n\n\u003cp align=\"center\"\u003e\n\n\u003csup\u003e\n\u003ca href=\"https://github.com/reframejs/wildcard-api/issues/new\"\u003eOpen a GitHub ticket\u003c/a\u003e\nif you have questions or something's not clear \u0026mdash; we enjoy talking with our users.\n\u003c/sup\u003e\n\n\u003cbr/\u003e\n\n\u003csup\u003e\n\u003ca href=\"#readme\"\u003e\u003cb\u003e\u0026#8679;\u003c/b\u003e \u003cb\u003eTOP\u003c/b\u003e \u003cb\u003e\u0026#8679;\u003c/b\u003e\u003c/a\u003e\n\u003c/sup\u003e\n\n\u003c/p\u003e\n\n\u003cbr/\u003e\n\u003cbr/\u003e\n\n\n\n## TypeScript\n\nYou can use your backend types on the frontend by using TypeScript's `typeof`.\n\n~~~ts\n// /examples/typescript/endpoints.ts\n\nimport { server as _server, FrontendType } from \"@wildcard-api/server\";\nimport { Context } from \"./context\";\n\ninterface Person {\n  firstName: string;\n  lastName: string;\n  id: number;\n}\n\nconst persons: Array\u003cPerson\u003e = [\n  { firstName: \"John\", lastName: \"Smith\", id: 0 },\n  { firstName: \"Alice\", lastName: \"Graham\", id: 1 },\n  { firstName: \"Harry\", lastName: \"Thompson\", id: 2 },\n];\n\nasync function getPerson(this: Context, id: number) {\n  if (!this.isLoggedIn) return null;\n  return persons.find((person) =\u003e person.id === id) || null;\n}\n\nconst server = {\n  getPerson,\n};\nexport type Server = FrontendType\u003ctypeof server, Context\u003e;\n\nObject.assign(_server, server);\n~~~\n~~~ts\n// /examples/typescript/client/index.ts\n\nimport \"babel-polyfill\";\nimport { Server } from \"../endpoints\";\nimport { server as serverUntyped } from \"@wildcard-api/client\";\n\nconst server = serverUntyped as Server;\n\n(async () =\u003e {\n  const id = Math.floor(Math.random() * 3);\n  const person = await server.getPerson(id);\n  if (person === null) {\n    document.body.innerHTML = \"Could not retrieve person\";\n  } else {\n    const personHtml =\n      person.firstName + \" \" + person.lastName + \" \u003cb\u003e(\" + person.id + \")\u003c/b\u003e\";\n    document.body.innerHTML = personHtml;\n  }\n})();\n~~~\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"#typescript\"\u003e\n    \u003cimg src=\"/examples/typescript/screenshots/types-on-frontend-1.png\" width=\"850\" height=\"195\" align=\"middle\" /\u003e\n    \u003cbr/\u003e\n    \u003cbr/\u003e\n    \u003cimg src=\"/examples/typescript/screenshots/types-on-frontend-2.png\" width=\"850\" height=\"212\" align=\"middle\" /\u003e\n  \u003c/a\u003e\n\u003c/p\u003e\n\nTypeScript usage examples:\n- [/examples/typescript/](/examples/typescript/)\n- [/examples/prisma/](/examples/prisma/)\n\n\n\u003cbr/\u003e\n\n\u003cp align=\"center\"\u003e\n\n\u003csup\u003e\n\u003ca href=\"https://github.com/reframejs/wildcard-api/issues/new\"\u003eOpen a GitHub ticket\u003c/a\u003e\nif you have questions or something's not clear \u0026mdash; we enjoy talking with our users.\n\u003c/sup\u003e\n\n\u003cbr/\u003e\n\n\u003csup\u003e\n\u003ca href=\"#readme\"\u003e\u003cb\u003e\u0026#8679;\u003c/b\u003e \u003cb\u003eTOP\u003c/b\u003e \u003cb\u003e\u0026#8679;\u003c/b\u003e\u003c/a\u003e\n\u003c/sup\u003e\n\n\u003c/p\u003e\n\n\u003cbr/\u003e\n\u003cbr/\u003e\n\n\n\n## API Documentation\n\nAPI browsing tools such as OpenAPI (formerly known as Swagger) makes sense for an API that is meant to be used by third-party developers who don't have access to your source code.\n\nA Wildcard API is meant to be used by your own developers;\ninstead of using OpenAPI,\nyou can give your frontend developers access to your backend code and save all endpoints in files named `endpoints.js`.\nThat way, a frontend developer can explore your API.\n\nFor improved developer experience,\nyou can use [Wildcard with TypeScript](#typescript) to make type hints available on the frontend.\nA frontend developer can then explore your Wildcard API directly in his IDE!\n\n\n\u003cbr/\u003e\n\n\u003cp align=\"center\"\u003e\n\n\u003csup\u003e\n\u003ca href=\"https://github.com/reframejs/wildcard-api/issues/new\"\u003eOpen a GitHub ticket\u003c/a\u003e\nif you have questions or something's not clear \u0026mdash; we enjoy talking with our users.\n\u003c/sup\u003e\n\n\u003cbr/\u003e\n\n\u003csup\u003e\n\u003ca href=\"#readme\"\u003e\u003cb\u003e\u0026#8679;\u003c/b\u003e \u003cb\u003eTOP\u003c/b\u003e \u003cb\u003e\u0026#8679;\u003c/b\u003e\u003c/a\u003e\n\u003c/sup\u003e\n\n\u003c/p\u003e\n\n\u003cbr/\u003e\n\u003cbr/\u003e\n\n\n\n## Caching\n\nWildcard automatically caches your endpoint results by using the HTTP ETag header.\nYou can disable caching by using the [`disableCache` option](#disablecache).\n\n\n\u003cbr/\u003e\n\n\u003cp align=\"center\"\u003e\n\n\u003csup\u003e\n\u003ca href=\"https://github.com/reframejs/wildcard-api/issues/new\"\u003eOpen a GitHub ticket\u003c/a\u003e\nif you have questions or something's not clear \u0026mdash; we enjoy talking with our users.\n\u003c/sup\u003e\n\n\u003cbr/\u003e\n\n\u003csup\u003e\n\u003ca href=\"#readme\"\u003e\u003cb\u003e\u0026#8679;\u003c/b\u003e \u003cb\u003eTOP\u003c/b\u003e \u003cb\u003e\u0026#8679;\u003c/b\u003e\u003c/a\u003e\n\u003c/sup\u003e\n\n\u003c/p\u003e\n\n\u003cbr/\u003e\n\u003cbr/\u003e\n\n\n\n## SSR\n\nThe Wildcard client is isomorphic (aka universal) and works in the browser as well as in Node.js.\n\nIf you don't need authentication, then SSR works out of the box.\nIf you do, then read [SSR \u0026 Authentication](/docs/ssr-auth.md#ssr--authentication).\n\n\n\u003cbr/\u003e\n\n\u003cp align=\"center\"\u003e\n\n\u003csup\u003e\n\u003ca href=\"https://github.com/reframejs/wildcard-api/issues/new\"\u003eOpen a GitHub ticket\u003c/a\u003e\nif you have questions or something's not clear \u0026mdash; we enjoy talking with our users.\n\u003c/sup\u003e\n\n\u003cbr/\u003e\n\n\u003csup\u003e\n\u003ca href=\"#readme\"\u003e\u003cb\u003e\u0026#8679;\u003c/b\u003e \u003cb\u003eTOP\u003c/b\u003e \u003cb\u003e\u0026#8679;\u003c/b\u003e\u003c/a\u003e\n\u003c/sup\u003e\n\n\u003c/p\u003e\n\n\u003cbr/\u003e\n\u003cbr/\u003e\n\n\n\n## Options\n\nAll options with their default value:\n~~~js\n// Browser (or Node.js)\n\nimport { config } from '@wildcard-api/client';\n\n// The URL of the Node.js server that serves the API\nconfig.serverUrl = null;\n\n// The base URL of Wildcard HTTP requests\nconfig.baseUrl = '/_wildcard_api/';\n\n// Whether the endpoint arguments are always passed in the HTTP body\nconfig.shortUrl = false;\n~~~\n~~~js\n// Node.js\n\nimport { config } from '@wildcard-api/server';\n\n// Whether Wildcard generates an ETag header.\nconfig.disableCache = false;\n\n// The base URL of Wildcard HTTP requests\nconfig.baseUrl = '/_wildcard_api/';\n~~~\n\n- [`serverUrl`](#serverurl)\n- [`baseUrl`](#baseUrl)\n- [`shortUrl`](#shorturl)\n- [`disableCache`](#disablecache)\n\n\u003cbr/\u003e\n\n### `serverUrl`\n\nYou usually don't need to provide any `serverUrl`.\nBut if your API and your browser-side assets are not served by the same server,\nthen you need to provide a `serverUrl`.\n\n`serverUrl` can be one of the following:\n- `null`. (Default value.)\n- The URL of the server, for example `http://localhost:3333/api` or `https://api.example.org`.\n- The IP address of the server, for example `92.194.249.32`.\n\nWhen `serverUrl` is `null`, the Wildcard client uses `window.location.origin` as server URL.\n\n~~~js\nimport { server, config } from '@wildcard-api/client';\nimport assert from 'assert';\n\nconfig.serverUrl = 'https://api.example.com:1337';\n\ncallEndpoint();\n\nasync function callEndpoint() {\n  await server.myEndpoint();\n\n  assert(window.location.origin==='https://example.com');\n  // Normally, Wildcard would make an HTTP request to the same origin:\n  //   POST https://example.com/_wildcard_api/myEndpoint HTTP/1.1\n\n  // But because we have set `serverUrl`, Wildcard makes\n  // the HTTP request to `https://api.example.com:1337` instead:\n  //   POST https://api.example.com:1337/_wildcard_api/myEndpoint HTTP/1.1\n};\n~~~\n\n\u003cbr/\u003e\n\n### `baseUrl`\n\nBy default, the pathname of any HTTP request that Wildcard makes starts with `/_willdcard_api/`.\nYou can change this base URL by using the `baseUrl` option.\n\n~~~js\nimport { server, config } from '@wildcard-api/client';\nimport assert from 'assert';\n\nconfig.baseUrl = '/_my_custom_api_base_url/';\n\ncallEndpoint();\n\nasync function callEndpoint() {\n  await server.myEndpoint();\n\n  assert(window.location.origin==='https://example.com');\n  // Normally, Wildcard would make an HTTP request to `/_wildcard_api/`:\n  //   POST https://example.com/_wildcard_api/myEndpoint HTTP/1.1\n\n  // But because we have changed `baseUrl`, Wildcard makes\n  // the HTTP request to `/_my_custom_api_base_url/` instead:\n  //   POST https://example.com/_my_custom_api_base_url/myEndpoint HTTP/1.1\n};\n~~~\n\nIf you change the `baseUrl` option of your Wildcard client,\nthen make sure that the `baseUrl` of your Wildcard server is the same:\n\n~~~js\nimport { config } from '@wildcard-api/server';\n\nconfig.baseUrl = '/_my_custom_api_base_url/';\n~~~\n\n\u003cbr/\u003e\n\n### `shortUrl`\n\nThe `shortUrl` option is about configuring whether\narguments are always passed in the HTTP request body.\n(Instead of being passed in the HTTP request URL.)\n\n~~~js\nimport { server, config } from '@wildcard-api/client';\n\nconfig.shortUrl = true; // Default value is `false`\n\ncallEndpoint();\n\nasync function callEndpoint() {\n  await server.myEndpoint({some: 'arguments' }, 'second arg');\n\n  // Normally, Wildcard would pass the arguments in the HTTP request URL:\n  //   POST /_wildcard_api/myEndpoint/[{\"some\":\"arguments\"},\"second arg\"] HTTP/1.1\n\n  // But because we have set `shortUrl` to `true`,\n  // Wildcard passes the arguments in the HTTP request body instead:\n  //   POST /_wildcard_api/myEndpoint HTTP/1.1\n  //   Request payload: [{\"some\":\"arguments\"},\"second arg\"]\n};\n~~~\n\n\u003cbr/\u003e\n\n### `disableCache`\n\nBy default Wildcard generates an HTTP ETag cache header.\nIf you need to save CPU computation time,\nyou can set `disableCache` to `true` and Wildcard will skip generating HTTP ETag headers.\n\n~~~js\nimport wildcardServer from '@wildcard-api/server';\n\nwildcardServer.disableCache = true;\n~~~\n\n\n\n\u003cbr/\u003e\n\n\u003cp align=\"center\"\u003e\n\n\u003csup\u003e\n\u003ca href=\"https://github.com/reframejs/wildcard-api/issues/new\"\u003eOpen a GitHub ticket\u003c/a\u003e\nif you have questions or something's not clear \u0026mdash; we enjoy talking with our users.\n\u003c/sup\u003e\n\n\u003cbr/\u003e\n\n\u003csup\u003e\n\u003ca href=\"#readme\"\u003e\u003cb\u003e\u0026#8679;\u003c/b\u003e \u003cb\u003eTOP\u003c/b\u003e \u003cb\u003e\u0026#8679;\u003c/b\u003e\u003c/a\u003e\n\u003c/sup\u003e\n\n\u003c/p\u003e\n\n\u003cbr/\u003e\n\u003cbr/\u003e\n\n\n\n## Learning Material\n\nMaterial to learn more about RPC and Wildcard.\n\n###### RPC\n\n- [What is RPC](/docs/what-is-rpc.md#what-is-rpc)\n  \u003cbr/\u003e\n  Explains what RPC is.\n- [FAQ](/docs/faq.md#faq)\n  \u003cbr/\u003e\n  FAQ about RPC.\n  Covers high-level questions such as \"Which is more powerful, GraphQL or RPC?\"\n  as well as low-level questions such as\n  \"How can I do versioning with RPC?\" or\n  \"Doesn't RPC tightly couple frontend with backend?\".\n- [What is the difference between REST and RPC?](/docs/blog/rest-rpc.md#readme)\n  \u003cbr/\u003e\n  This post explains what REST, RPC, and RPC-like means.\n\n###### Blog\n\n- [RPC as Default](/docs/blog/rpc-as-default.md#rpc-as-default)\n- [REST or GraphQL? A simple answer.](/docs/blog/rest-or-graphql.md#rest-or-graphql-a-simple-answer)\n\n###### Wildcard\n\n- [How Wildcard Works](/docs/how-wildcard-works.md#how-wildcard-works)\n  \u003cbr/\u003e\n  Talks about the technologies Wildcard uses under the hood.\n- [Example - A Todo List](/examples/todo-list/#example---a-todo-list)\n  \u003cbr/\u003e\n  Showcases a to-do list app built with RPC/Wildcard.\n- [SSR \u0026 Authentication](/docs/ssr-auth.md#ssr--authentication)\n  \u003cbr/\u003e\n  How to use Wildcard with SSR and Authentication.\n\n\n\u003cbr/\u003e\n\n\u003cp align=\"center\"\u003e\n\n\u003csup\u003e\n\u003ca href=\"https://github.com/reframejs/wildcard-api/issues/new\"\u003eOpen a GitHub ticket\u003c/a\u003e\nif you have questions or something's not clear \u0026mdash; we enjoy talking with our users.\n\u003c/sup\u003e\n\n\u003cbr/\u003e\n\n\u003csup\u003e\n\u003ca href=\"#readme\"\u003e\u003cb\u003e\u0026#8679;\u003c/b\u003e \u003cb\u003eTOP\u003c/b\u003e \u003cb\u003e\u0026#8679;\u003c/b\u003e\u003c/a\u003e\n\u003c/sup\u003e\n\n\u003c/p\u003e\n\n\u003cbr/\u003e\n\u003cbr/\u003e\n\n\n\n\n\u003c!---\n\n\n\n\n\n\n    WARNING, READ THIS.\n    This is a computed file. Do not edit.\n    Instead, edit `/docs/readme.template.md` and run `npm run docs` (or `yarn docs`).\n\n\n\n\n\n\n\n\n\n\n\n\n    WARNING, READ THIS.\n    This is a computed file. Do not edit.\n    Instead, edit `/docs/readme.template.md` and run `npm run docs` (or `yarn docs`).\n\n\n\n\n\n\n\n\n\n\n\n\n    WARNING, READ THIS.\n    This is a computed file. Do not edit.\n    Instead, edit `/docs/readme.template.md` and run `npm run docs` (or `yarn docs`).\n\n\n\n\n\n\n\n\n\n\n\n\n    WARNING, READ THIS.\n    This is a computed file. Do not edit.\n    Instead, edit `/docs/readme.template.md` and run `npm run docs` (or `yarn docs`).\n\n\n\n\n\n\n\n\n\n\n\n\n    WARNING, READ THIS.\n    This is a computed file. Do not edit.\n    Instead, edit `/docs/readme.template.md` and run `npm run docs` (or `yarn docs`).\n\n\n\n\n\n\n--\u003e\n","funding_links":[],"categories":["JavaScript"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbrillout%2Fwildcard-api","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbrillout%2Fwildcard-api","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbrillout%2Fwildcard-api/lists"}