wildcard-api

0.7.3 • Public • Published

Wildcard API



What is Wildcard

Wildcard is a JavaScript library to create an API between your Node.js server and your frontend.

With Wildcard, creating an API endpoint is as easy as creating a JavaScript function:

// Node.js server
 
const {endpoints} = require('wildcard-api');
 
// We define a `hello` function on the server
endpoints.hello = function(name) {
  return {message: 'Welcome '+name};
};
// Browser
 
import {endpoints} from 'wildcard-api/client';
 
(async () => {
  // Wildcard makes our `hello` function available in the browser
  const {message} = await endpoints.hello('Alice');
  console.log(message); // Prints `Welcome Alice`
})();

That's all Wildcard does: it makes functions, that are defined on your Node.js server, "callable" in the browser. Nothing more, nothing less.

How you retrieve/mutate data is up to you; you can use any SQL/NoSQL/ORM query:

// Node.js server
 
const endpoints = require('wildcard-api');
const getLoggedUser = require('./path/to/your/auth/code');
const Todo = require('./path/to/your/data/model/Todo');
 
endpoints.createTodoItem = async function(text) {
  const user = await getLoggedUser(this.headers); // We talk about `this` later.
 
  if( !user ) {
    // The user is not logged-in.
    // We abort.
    // (This is basically how you define permissions with Wildcard
    // which we will talk more about later.)
    return;
  }
 
  // With an ORM/ODM:
  const newTodo = new Todo({text, authorId: user.id});
  await newTodo.save();
  /* Or with SQL:
  const db = require('your-favorite-sql-query-builder');
  const [newTodo] = await db.query(
    "INSERT INTO todos VALUES (:text, :authorId);",
    {text, authorId: user.id}
  );
  */
 
  return newTodo;
};

Wildcard is new but already used in production at couple of projects, every release is assailed against a heavy suit of automated tests, its author is responsive, and issues are fixed within 1-2 days.


Open a ticket or chat with us if you have questions, feature requests, or if you just want to talk to us. We enjoy talking with our users.
TOP



Wildcard compared to REST, GraphQL, and other RPCs

Wildcard is an RPC tool. While REST and GraphQL shine for APIs that are meant to consumed by third parties, RPC is increasingly used for internal APIs, prototypes, and apps with a frontend and backend developed hand-in-hand.

Large companies, such as Netflix and Google, are starting to replace REST/GraphQL with RPC for their internal APIs. Most notably with gRPC which is increasingly popular in the industry.

Both gRPC and Wildcard are RPC tools. While gRPC focuses on cross-platform support (Go, Python, Java, C++, etc.), Wildcard only supports the Browser - Node.js stack. This allows Wildcard to have a simple design (with a mere 1.1K-LOCs) and to be super easy to use.

Wildcard's simplicity and flexibility excel most for prototypes that quickly evolve.

If you are a full-stack JavaScript developer and your frontend is the only consumer of your backend's API, then Wildcard is, compared to REST/GraphQL, superior in virtually every way.

If you are unfamiliar with RPC, then check out RPC vs REST/GraphQL.


Open a ticket or chat with us if you have questions, feature requests, or if you just want to talk to us. We enjoy talking with our users.
TOP



Getting Started

This getting started is about adding Wildcard to an exisiting app. If you don't already have an app or if you just want to try out Wildcard, you can use a Reframe starter to quickly get started.

  1. Add Wildcard to your Node.js server.

    With Express:

    // Node.js server
     
    const express = require('express');
    const {getApiResponse} = require('wildcard-api'); // npm install wildcard-api
     
    const app = express();
     
    // Parse the HTTP request body
    app.use(express.json());
     
    app.all('/wildcard/*' , async (req, res) => {
      // `getApiResponse` requires the HTTP request `url`, `method`, and `body`.
      const requestProps = {
        url: req.url,
        method: req.method,
        body: req.body,
      };
     
      // The `requestProps` object is available in your endpoint functions as `this`.
      // For example, you can add `req.headers` to `requestProps` to be
      // able to access it in your endpoint functions as `this.headers`.
      requestProps.headers = req.headers;
     
      const responseProps = await getApiResponse(requestProps);
      res.status(responseProps.statusCode);
      res.type(responseProps.contentType);
      res.send(responseProps.body);
    });
    With Hapi
    // Node.js server
     
    const Hapi = require('hapi');
    const {getApiResponse} = require('wildcard-api'); // npm install wildcard-api
     
    const server = Hapi.Server();
     
    server.route({
      method: '*',
      path: '/wildcard/{param*}',
      handler: async (request, h) => {
        // `getApiResponse` requires the HTTP request `url`, `method`, and `body`.
        const requestProps = {
          url: request.url,
          method: request.method,
          body: request.payload,
        };
     
        // The `requestProps` object is available in your endpoint functions as `this`.
        // For example, you can add `request.headers` to `requestProps` to be
        // able to access it in your endpoint functions as `this.headers`.
        requestProps.headers = request.headers;
     
        const responseProps = await getApiResponse(requestProps);
        const response = h.response(responseProps.body);
        response.code(responseProps.statusCode);
        response.type(responseProps.contentType);
        return response;
      }
    });
    With Koa
    // Node.js server
     
    const Koa = require('koa');
    const Router = require('koa-router');
    const bodyParser = require('koa-bodyparser');
    const {getApiResponse} = require('wildcard-api'); // npm install wildcard-api
     
    const app = new Koa();
     
    // Parse the HTTP request body
    app.use(bodyParser());
     
    const router = new Router();
     
    router.all('/wildcard/*', async ctx => {
      // `getApiResponse` requires the HTTP request `url`, `method`, and `body`.
      const requestProps = {
        url: ctx.url,
        method: ctx.method,
        body: ctx.request.body,
      };
     
      // The `requestProps` object is available in your endpoint functions as `this`.
      // For example, you can add `ctx.request.headers` to `requestProps` to be
      // able to access it in your endpoint functions as `this.headers`.
      requestProps.headers = ctx.request.headers;
     
      const responseProps = await getApiResponse(requestProps);
      ctx.status = responseProps.statusCode;
      ctx.body = responseProps.body;
      ctx.type = responseProps.contentType;
    });
     
    app.use(router.routes());
    With other server frameworks

    Wildcard can be used with any server framework. All you have to do is to reply all HTTP requests made to /wildcard/* with getApiResponse:

    // Node.js server
     
    // This is generic pseudo code for how to integrate Wildcard with any server framework.
     
    const {getApiResponse} = require('wildcard-api'); // npm install wildcard-api
     
    const {addRoute, HttpResponse} = require('your-favorite-server-framework');
     
    // Add a new route `/wildcard/*` to your server
    addRoute(
      '/wildcard/*',
      async ({req}) => {
        // We assume that your server framework provides an object holding
        // information about the request. We denote this object `req`.
     
        // `getApiResponse` requires the HTTP request `url`, `method`, and `body`.
        const requestProps = {
          url: req.url, // The HTTP request url (or pathname)
          method: req.method, // The HTTP request method (`GET`, `POST`, etc.)
          body: req.body, // The HTTP request body
        };
     
        // The `requestProps` object is available in your endpoint functions as `this`.
        // For example, you can add `req.headers` to `requestProps` to be
        // able to access it in your endpoint functions as `this.headers`.
        requestProps.headers = req.headers;
     
        // We get the HTTP response body, HTTP status code, and the body's content type.
        const responseProps = await getApiResponse(requestProps);
        const {body, statusCode, contentType} = responseProps;
     
        // We assume that server framework provides a way to create an HTTP response
        // upon `body`, `statusCode`, and `contentType`.
        const response = new HttpResponse({body, statusCode, contentType});
        return response;
      }
    );
  2. Define an endpoint function in Node.js:

    // Node.js server
     
    const {endpoints} = require('wildcard-api');
     
    endpoints.myFirstEndpoint = async function () {
      // The `this` object is the `requestProps` object we passed to `getApiResponse`. Because
      // the headers are set on `requestProps.headers`, we can access them over `this.headers`.
      // You can then, for example, use the headers for authentication.
      console.log('The HTTP request headers:', this.headers);
     
      return {msg: 'hello from my first Wildcard endpoint';
    };
  3. You can now "call" your enpdoint function from you frontend:

    // Browser
     
    import {endpoints} from 'wildcard-api/client'; // npm install wildcard-api
     
    (async () => {
      const {msg} = await endpoints.myFirstEndpoint();
      console.log(msg);
    })();

Open a ticket or chat with us if you have questions, feature requests, or if you just want to talk to us. We enjoy talking with our users.
TOP



Authentication

Authentication usually uses HTTP headers such as Authorization: Bearer AbCdEf123456 or a cookie holding the user's session ID.

You can access the HTTP request headers in your endpoint functions by passing the headers object to getApiResponse:

// Node.js server
 
app.all('/wildcard/*' , async (req, res) => {
  const requestProps = {
    url: req.url,
    method: req.method,
    body: req.body,
    // We pass the `headers` object
    headers: req.headers,
  };
  const responseProps = await getApiResponse(requestProps);
  res.status(responseProps.statusCode);
  res.type(responseProps.contentType);
  res.send(responseProps.body);
});

Wildcard makes requestProps available to your endpoint function as this:

// Node.js server
 
const {endpoints} = require('wildcard-api');
const getUser = require('./path/to/your/auth-code/getUser');
 
endpoints.getLoggedInUser = async function() {
  // Since `this===requestProps`, `requestProps.headers` is available as `this.headers`.
  const user = await getUser(this.headers.cookie);
  return user;
};

If you do SSR, an additional step needs to be done in order to make authentication work, see SSR & Authentication.


Open a ticket or chat with us if you have questions, feature requests, or if you just want to talk to us. We enjoy talking with our users.
TOP



Permissions

Permission is defined by code. For example:

// Node.js server
 
const {endpoints} = require('wildcard-api');
const getLoggedUser = require('./path/to/your/auth/code');
const db = require('./path/to/your/db/handler');
 
// The following endpoint showcases how to implement permissions with Wildcard.
// The endpoint only allows the author of a todo-item to modify it.
 
endpoints.updateTodoText = async function(todoId, newText) {
  if( !user ) {
    // The user is not logged-in.
    // We abort.
    return;
  }
 
  const todo = await db.getTodo(todoId);
 
  if( !todo ) {
    // `todoId` didn't match any todo.
    // We abort.
    return;
  }
 
  if( todo.authorId !== user.id ) {
    // The user is not the author of the to-do item.
    // We abort.
    return;
  }
 
  // The user is logged-in and is the author of the todo.
  // We commit the new to-do text.
  await db.updateTodoText(todoId, newText);
};

See the to-do list app example for further permission examples.


Open a ticket or chat with us if you have questions, feature requests, or if you just want to talk to us. We enjoy talking with our users.
TOP



Error Handling

Calling an endpoint throws an error when:

  • The browser cannot connect to the server. (The user is offline or your server is down.)
  • The endpoint function throws an uncaught error.

If you use a library that is expected to throws errors, then catch them:

// Node.js server
 
const {endpoints} = require('wildcard-api');
const validatePhoneNumber = require('some-phone-number-validatation-library');
 
endpoints.createAccount = async function({email, phoneNumber}) {
  // `validatePhoneNumber` throws an error if `phoneNumber` is not a phone number.
  let err;
  try {
    validatePhoneNumber(phoneNumber);
  } catch(err_) {
    err = err_
  }
  if( err ) {
    return {validationError: {phoneNumber: "Please enter a valid phone number."}};
  }
 
  /* ... */
};

You should always catch expected errors: Wildcard treats any uncaught error as a bug in your code.

In particular, don't throw an error upon validation failure:

// Node.js server
 
const {endpoints} = require('wildcard-api');
const isStrongPassword = require('./path/to/isStrongPassword');
 
endpoints.createAccount = async function({email, password}) {
  /* Don't do this:
  if( !isStrongPassword(password) ){
    throw new Error("Password is too weak.");
  }
  */
 
  // Instead, return a JavaScript value, e.g. a JavaScript object:
  if( !isStrongPassword(password) ){
    return {validationError: "Password is too weak."};
  }
 
  /* ... */
};

You can use isServerError and isNetworkError to handle errors more precisely:

// Browser
 
import {endpoints} from 'wildcard-api/client';
 
async function() {
  let data;
  let err;
  try {
    data = await endpoints.getData();
  } catch(err_) {
    err = err_;
  }
 
  if( err.isServerError ){
    // Your endpoint function throwed an uncaught error: there is a bug in your server code.
    alert(
      'Something went wrong on our side. We have been notified and we are working on a fix.' +
      'Sorry... Please try again later.'
    );
  }
  if( err.isNetworkError ){
    // The browser couldn't connect to the server.
    // The user is offline or your server is down.
    alert("We couldn't perform your request. Please try again.");
  }
 
  if( err ) {
    return {success: false};
  } else {
    return {success: true, data};
  }
}

You can also use Handli which will automatically handle errors for you:

// Browser
 
import 'handli'; // npm install handli
// That's it: Wildcard will automatically use Handli.
// Errors are now handled by Handli.

Open a ticket or chat with us if you have questions, feature requests, or if you just want to talk to us. We enjoy talking with our users.
TOP



SSR

The Wildcard client is isomorphic (aka universal) and works in the browser as well as in Node.js.

If you don't need authentication, then SSR works out of the box.

Otherwise read SSR & Authentication.


Open a ticket or chat with us if you have questions, feature requests, or if you just want to talk to us. We enjoy talking with our users.
TOP



Options

ℹ️ If you need an option that Wildcard is missing, then open a new GitHub issue. We usually implement new options within 1-2 days.

Overview of all options:

import {WildcardClient} from 'wildcard-api/client';
 
const endpoints = new WildcardClient({
  // The URL of the Node.js server that serves the API
  serverUrl: null, // Default value
 
  // Whether the endpoint arguments are always passed in the HTTP body
  argumentsAlwaysInHttpBody: false, // Default value
});

More details about each option:


serverUrl

Wildcard automatically determines the adress of the server and you don't need to provide serverUrl.

But if the Node.js server that serves the API is not the same server that serves your browser-side assets, then you need to provide serverUrl.

For example:

import {WildcardClient} from 'wildcard-api/client';
// (Or `const {WildcardClient} = require('wildcard-api/client');`)
import assert from 'assert';
 
const endpoints = new WildcardClient({
  serverUrl: 'https://api.example.com:1337',
});
 
callEndpoint();
 
async function callEndpoint() {
  await endpoints.myEndpoint();
 
  assert(window.location.origin==='https://example.com');
 
  // Normally, Wildcard makes HTTP requests to the same origin:
  //   POST https://example.com/wildcard/myEndpoint HTTP/1.1
 
  // But because we have set `serverUrl`, Wildcard makes
  // the HTTP requests to `https://api.example.com:1337`:
  //   POST https://api.example.com:1337/wildcard/myEndpoint HTTP/1.1
};

argumentsAlwaysInHttpBody

argumentsAlwaysInHttpBody is about configuring whether arguments are always passed in the HTTP request body. (Instead of being passed in the HTTP request URL.)

For example:

import {WildcardClient} from 'wildcard-api/client';
// (Or `const {WildcardClient} = require('wildcard-api/client');`)
 
const endpoints = new WildcardClient({
  argumentsAlwaysInHttpBody: true,
});
 
callEndpoint();
 
async function callEndpoint() {
  await endpoints.myEndpoint({some: 'arguments' }, 'second arg');
 
  // Normally, Wildcard passes the arguments in the HTTP request URL:
  //   POST /wildcard/myEndpoint/[{"some":"arguments"},"second arg"] HTTP/1.1
 
  // But because we have set `argumentsAlwaysInHttpBody` to `true`,
  // Wildcard passes the arguments in the HTTP request body instead:
  //   POST /wildcard/myEndpoint HTTP/1.1
  //   Request payload: [{"some":"arguments"},"second arg"]
};

Open a ticket or chat with us if you have questions, feature requests, or if you just want to talk to us. We enjoy talking with our users.
TOP



More Resources

This section collects further information about Wildcard.

  • SSR & Authentication
    How to use Wildcard with SSR and Authentication.

  • How Does It Work
    Explains how Wildcard works.

  • Conceptual FAQ
    High level discussion about Wildcard, RPC APIs, GraphQL, and REST.

  • To-do List Example
    An example of a to-do list app implemented with Wildcard.

  • Custom VS Generic
    Goes into depth of whether you should implement a generic API (REST/GraphQL), or a custom API (Wildcard), or both. In general, the rule of thumb for deciding which one to use is simple: if third parties need to access your data, then implement a generic API, otherwise implement a custom API. But in certain cases it's not that easy and this document goes into more depth.


Open a ticket or chat with us if you have questions, feature requests, or if you just want to talk to us. We enjoy talking with our users.
TOP



Readme

Keywords

none

Package Sidebar

Install

npm i wildcard-api

Weekly Downloads

2

Version

0.7.3

License

none

Unpacked Size

339 kB

Total Files

14

Last publish

Collaborators

  • brillout