# apiiz

> Create API mappings with ease

Latest version **0.2.3** (published 2022-08-03) · ISC license · 0 weekly downloads

## Install

```sh
npm install apiiz
pnpm add apiiz
yarn add apiiz
bun add apiiz
```

## Health

**Score 25/100 (F)** — status: abandoned.

Positive: has types; no vulnerabilities; high quality score.

Warnings: low downloads; no esm support; pre 1.0.

Negative: abandoned; low maintenance score.

## Facts

| | |
|---|---|
| Version | 0.2.3 |
| Published | 2022-08-03 |
| First published | 2022-05-06 |
| Weekly downloads | 0 |
| License | ISC |
| TypeScript types | bundled |
| Module format | CommonJS |
| Dependencies | 1 |
| Unpacked size | 101.9 KB |
| Known vulnerabilities | 0 (+23 in 1 direct dependencies) |
| Install scripts | no |
| GitHub stars | 1 |
| Author | linq2js |
| Maintainers | linq2js |

## Links

- npm: https://www.npmjs.com/package/apiiz
- Repository: https://github.com/linq2js/apiiz
- Homepage: https://linq2js.github.io/apiiz/
- Issues: https://github.com/linq2js/apiiz/issues
- npm.io page: https://npm.io/package/apiiz

## Dependencies (1)

- [axios](https://npm.io/package/axios.md) ^0.27.2

## Recent versions

- 0.2.3 (latest) — 2022-08-03
- 0.2.2 — 2022-08-03
- 0.2.1 — 2022-08-03
- 0.2.0 — 2022-07-29
- 0.1.9 — 2022-06-30
- 0.1.8 — 2022-06-30
- 0.1.7 — 2022-05-26
- 0.1.6 — 2022-05-26
- 0.1.5 — 2022-05-26
- 0.1.4 — 2022-05-26
- 0.1.3 — 2022-05-18
- 0.1.2 — 2022-05-17
- 0.1.1 — 2022-05-08
- 0.1.0 — 2022-05-08
- 0.0.13 — 2022-05-07
- … 4 more at https://npm.io/package/apiiz/versions

## README

# `apiiz`

Create API mappings with ease

## Installation

**with NPM**

```bash
npm i apiiz --save
```

**with YARN**

```bash
yarn add apiiz
```

## Peer dependencies

- Install dataloader if you use loader resolver (see example)

## Features

1. REST API supported
1. GraphQL API supported
1. Dataloader supported
1. Enhancers supported
1. Highly extensible
1. Fully Typescript supported
1. Relation loading supported

## Usages

### Defining REST API

```js
import { define } from "apiiz";
import { rest } from "apiiz/rest";

const api = define({
  getUserById: rest("https://yourserver.com/api/getUserById/{id}", {
    // convert api payload to URL params
    params: (payload) => ({ id: payload }),
  }),
  // if your API requires user id as query param, just do this
  getUserById2: rest("https://yourserver.com/api/getUserById", {
    // convert api payload to URL params
    query: (id) => ({ id }),
  }),
});

// apiiz will generate an object that has getUserById method
// a API mapped method returns a promise object. The promise object will resolve when request completed and reject if there is any HTTP error
api.getUserById(1);
```

### Defining GraphQL API

```js
import { define } from "apiiz";
import gql from "graphql-tag";
import { graphql } from "apiiz/graphql";

const getMediaByIdQuery = gql`
  query GetMediaById($id: Int!) {
    Media(id: $id) {
      id
      startDate {
        year
        month
        day
      }
      season
      tags {
        id
      }
    }
  }
`;

const api = define({
  // graphql() can retrieve query object or query string
  getMediaById: graphql(getMediaByIdQuery, {
    url: "https://yourserver.com/graphql",
    // map payload to variables
    vars: (payload) => ({ id: payload }),
  }), // this API will return { Media: { id, name, ... } }
  getMediaById2: graphql(
    getMediaByIdQuery,
    // select Media prop of data object that returned from server
    "Media",
    {
      url: "https://yourserver.com/graphql",
      vars: (payload) => ({ id: payload }),
    } // this API will return { id, name, ... }
  ),
});
```

### Using HTTP configs

```js
const api = define({
  configs: {
    // headers builder
    headers: () => ({
      // pass access token that is saved in localStorage
      authorization: localStorage.getItem("token"),
    }),
    http: { baseUrl: "https://yourserver.com/api" },
  },
  // the url will prepend baseUrl from http configs
  getUserById: rest("/getUserById/{id}", { params: (id) => ({ id }) }),
});
```

### Using dataloader

apiiz uses dataloader package for handling batch requests. Let say you have an API get users by id list

```js
import { loader } from "apiiz/loader";
import { rest } from "apiiz/rest";
const api = define({
  configs: { http: { baseUrl: "https://yourserver.com/api" } },
  // the url will prepend baseUrl from http configs
  getUserById: loader(
    rest("/getUsers", {
      method: "post",
      // passing ids to request body
      body: (ids) => ({ ids }),
    }),
    {
      // if you are not sure the user list that is returned from server has same order with id, use remap() to make it right
      remap: (user, id) => user.id === id,
      // other dataloader options
    }
  ),
});

api.getUserById(1);
api.getUserById(2);
api.getUserById(1); // duplicated
api.getUserById(3);
// all getUserById requests will be delayed in awhile and only one getUsers request sends to server
```

### Handling errors

```js
const api = define({
  configs: {
    // this is global error handler, it will be called by all API providers (rest, graphql)
    onError(e) {
      console.log(e);
    },
    // by default, dismissErrors = false, if you set dismissErrors = true, all errors are dismissed and the API will return the promise that runs forever. The error handlers still recieve errors
    dismissErrors: false,
    ...rest.configs({
      // this is error handler for REST API only
      onError(e) {},
    }),
    ...graphql.configs({
      // this is error handler for GraphQL API only
      onError(e) {},
    }),
  },
});
```

### Using enhancers

apiiz provides enhancer machanism, where you can control requests working flow with ease

```js
import { use, define } from "apiiz";
import { rest } from "apiiz/rest";

// simple cache enhancer
const cache = (originalResolver, keyFactory) => (context) => {
  const originalDispatcher = originalResolver(context);
  const cacheStorage = context.__cache || (context.__cache = {});
  // return a new dispatcher
  return (payload) => {
    // generate the cache key
    const key = keyFactory(payload);
    // if the key is not exist in the cache, call originalDispatcher to get result and store the result to the cache
    if (!cacheStorage[key]) {
      cacheStorage[key] = originalDispatcher(payload);
    }
    return cacheStorage[key];
  };
};

const api = define({
  getUserById: enhance(rest("/getUserById")).with(
    cache,
    // this arg will pass to cache()
    (payload) =>
      // generate cache key according to api payload
      `getUserById:${payload}`
  ),
});

const p1 = api.getUserById(1);
const p1 = api.getUserById(1);
const p1 = api.getUserById(1);
console.log(p1 === p2); // true
// only one request sent
```

### Validating payload

You can create validate() enhancer that uses Joi/Yup for validating API payload

```js
import { define, enhance } from "apiiz";
import { memory } from "apiiz/memory";
import Joi from "joi";

// a validate fn retrieves Joi schema
const validate = (resolver, schema) =>
  // using this overload of enhance() to create new enhancer quickly
  enhance(resolver, (context, dispatcher, payload) => {
    // validate the payload and correct payload types if possible
    const result = schema.validate(payload, {
      convert: true,
      abortEarly: true,
    });
    // throw error if any
    if (result.error) throw result.error;
    // using the validated value as new payload
    return dispatcher(result.value);
  });
const database = [];
const api = define({
  addNumber: enhance(
    // create a memory API, just push a value to database object
    memory((value) => database.push(value))
  ).with(
    validate,
    // payload must be number
    Joi.number()
  ),
});

api.addNumber(1);
api.addNumber(2);
api.addNumber("3"); // '3' will be converted to 3
api.addNumber("abcdef"); // an error will be thrown
```

### Server APIs

apiiz can be used for both backend and frontend, you can take advantages of data batch loading, caching, validating

```js
import prisma from "./prisma";
import { define, enhance } from "apiiz";
import { memory } from "apiiz/memory";
import { validate } from "./enhancers/validate";
import Joi from "joi";

// create a simple enhancer for route registration
const route = (resolver, method, path, payloadSelector) => {
  return (context) => {
    // get express app object from configs
    const app = context.configs.app;
    const dispatcher = resolver(dispatcher);
    app[method](async (req, res) => {
      // get payload from req
      const payload = payloadSelector(req);
      // dispatch API body and send the result/error to client
      try {
        res.json(dispatcher(payload));
      } catch (error) {
        res.status(400).json(error);
      }
    });
    return dispatcher;
  };
};

const api = define({
  getUser: enhance(
    // using loader for batch loading
    loader(
      // you can create new prisma API creator in this case
      // we use memory for basic example
      memory((ids) => prisma.user.findMany({ where: { id: { in: ids } } })),
      // the users that returned from prisma might has wrong order, so need to re-order those users
      { remap: (user, id) => user.id === id }
    )
  )
    // validate payload
    .with(validate, Joi.number())
    // the route enhancer must be last one to make sure it can receive all enhanced results of previous ones
    .with(route, "get", "/user/:id", (req) => req.params.id),
});
```

### Handle concurrency

```js
import { debounce } from "apiiz/concurrency";

const api = define({
  updateUserProfile: enhance(
    // rest API creator also provides shorthands for HTTP methods: put get post delete options patch head
    rest.post("/user/{id}/update", { params: (id) => ({ id }) })
  )
    // use debounce enhancer to delay API call in 300ms
    .with(debounce, 300),
});
```

Concurrency module also provide throttle() enhancer

```js
import { throttle } from "apiiz/concurrency";

const api = define({
  trackUserActivity: enhance(
    // rest API creator also provides shorthands for HTTP methods: put get post delete options patch head
    rest.post("/user/{id}/track", { params: (id) => ({ id }) })
  ).with(throttle, 300),
});
```

### Loading relations

```js

```

## API References

https://linq2js.github.io/apiiz/

---
_Source: https://npm.io/package/apiiz · Machine-readable twin of the npm.io package page. Health data is recomputed on every publish._
