4.1.0 • Published 2 years ago

@apparts/api v4.1.0

Weekly downloads
3
License
UNLICENSED
Repository
-
Last release
2 years ago

#+TITLE: @apparts/api #+DATE: 2019-02-07 Thu #+AUTHOR: Philipp Uhl

  • Setup

To tell the =@apparts/api= package, how to work with your API, you have to overload the =Request= class. If you are using tokens for users to be authenticated (e.g. when using =@apparts/login-server=) you also need to overload the =Token= class and tell the =Request= class to use the =Token= class in a custom authentication method (compare =authUser= below).

Finally, give your new =Request= class to =useApi= and export the functions that you recieve.

#+BEGIN_SRC js import {useApi, Token, Request} from "@apparts/api";

const APIVERSION = 1; const URL = DEV // eslint-disable-line no-undef ? "https://devendpoint/v/" : "https://prodendpoint/v/";

const logout = () => { // Log out user on 401 HTTP-Error window.location.href = "/login?force=true"; };

class MyToken extends Token { constructor(user) { super(user);

  // Tell Token where to find the users api token, should you
  // already have one
  this._apiToken = user.apiToken;
}

async renewAPIToken(user) {
  // Tell Token how to renew the API Token
  const apiToken = await get("user/apiToken").authPW(
    user.email,
    user.loginToken
  );
  return apiToken;
}

static getUserKey(user) {
  // Tell Token how to identify users
  return user.email;
}

}

class MyRequest extends Request { getURL(apiVersion) { // Tell Request what the URL prefix is return URL + apiVersion; } getAPIVersion() { // Tell Request what the default APIVersion is return APIVERSION; }

online() {
  // Will be called, when a network-call succeded
}

authUser(user) {
  // Define a method for authenticating with a user token.
  // This will be called by you, when you want to authenticate with a user
  this._auth = MyToken.getAPIToken(user);
  return this;
}

async middleware() {
  // Tell Request what to do on recieving not-yet caught errors, that should be
  // handled globally.

  this.on(410, () => alert("Your website is out of date, please reload it."));
  this.on({ status: 401, error: "Token invalid" }, () => { throw "Invalid token"; });
  this.on(401, logout);
  this.on(0, () => alert("We could not reach the server. Are you online?"));
}

}

const { get, put, patch, post, del } = useApi(MyRequest); export { get, put, patch, post, del }; #+END_SRC

  • Usage

The functions =get=, =put=, =patch=, =post=, =del= have this signature (exemplary for =get=):

  • =get(url: , urlParams: <+array>): Request= :: The url can be parameterized with =$1=, =$2=, ... Each =$=-prefixed number will be replaced with the corresponding element from the array: =$1= will be replaced with =urlParams0=, =$2= with =urlParams1=, and so on. A parameter can be used multiple times in the =url=-string. If not all or too many parameters are used, an error will be thrown.

The functions =get=, =put=, =patch=, =post=, =del= return a =Request=. Requests have the following methods:

  • =query(params: ): Request= :: Set query params as key value pairs. : r.query({ filter: "4", a: "b" }) // will be https://yourendpoint/path/to/endpoint?filter=4&a=b
  • =data(data: ): Request= :: Set the body data
  • =settings(settings: ): Request= :: Set =axios= settings
  • =v(v: int): Request= :: Overwrite APIVersion, used for this request
  • =auth(auth: ): Request= :: Set the =axios=-auth param
  • =authPW(username: , password: ): Request= :: Use basic auth
  • =authAPIKey(token: ): Request= :: Use bearer auth
  • =on(status: <int | object>, next: ): Request= :: Attach an error handler:
    • Status parameter:
      • If used with =status= as an integer, matches all HTTP-Errors with that status.
      • If used with =status= as an object of the form ={ status: , error: }=, matches all HTTP-Errors with that status and an object with a matching error-key in the body.
    • When such an error occurs, =next(errorJson, error)= will be called with =errorJson= being the parsed error and =error= the raw =axios= error.
    • Multiple error catchers can be appended. The first one to match (in order of attaching) will be executed.
    • When error has been caught, =catch= will be called, but receives =false= as an error.
  • =then(): Promise= :: Then
  • =catch(): Promise= :: Catch
  • =finally(): Promise= :: Finally

Example:

#+BEGIN_SRC js try { const resp = await put("users/$1/name") .data({ name: "John" }) .userAuth(user) .on({ status: 400, error: "Too short" }, () => { alert("Please choose a longer username."); }) .on({ status: 400, error: "Is taken" }, () => { alert("This username is taken, already. Please choose a different username."); }); } catch (e) { // If e is not false, then, no error-catcher caught the error and // you might want to take care of it e && alert(e);

// Do, what you have to do on an error. Catch will be called, even
// when the error was caught by an error catcher. If you have some
// error-unspecific cleanup to do, this would be a good place:

/* setLoading(false); */

} #+END_SRC

  • Generate API SDK

=@apparts/api= supports generating a fully typed TypeScript SDK to access an API that is defined through an API description as generated by =@apparts/prep=.

You might want to install =prettier= (=npm i -D prettier=) to improve the output.

To generate the SDK, run the following:

#+BEGIN_SRC js import * as prettier from "prettier"; const prettify = (src) => prettier.format(src, { parser: "typescript" });

// The API definition as output by the getApi function from @apparts/prep import { testApi } from "./api-description.json"; import { genFile, EndpointDefinition } from "@apparts/api";

// Pipe to file or write to fs from here console.log(prettify(genFile(testApi.routes as EndpointDefinition[]))); #+END_SRC

The resulting API SDK code exports the function =createApi= which expects one parameter: the api as exported from =useApi(MyRequest)= as setup above.

In your application:

#+BEGIN_SRC js import { createApi } from "./"

// setup MyRequest, etc. const apiRaw = useApi(MyRequest); export const api = createApi(apiRaw); #+END_SRC

The resulting api object contains all API endpoints in the following manner:

An endpoint =GET /v/1/user/:userId/info= with the returns

  • code 200, ==
  • code 404, ={ error: "User not found" }=
  • code 404, ={ error: "User info not found" }=

Can be accessed like this:

#+BEGIN_SRC js try { const res = await api.user.info.get({ params: { userId }, / data, query /}) // Optional error catchers. Matching checked in order of function // usage. So in this example, first the catcher for { status: 404, // error: "user not found" } is checked, then { status: 404, error: // "user info not found" }, at last all status 404 responses. .on404UserNotFound(/ catcher fn /) .on404UserInfoNotFound(/ catcher fn /) .on404(/ catcher fn /) // catch all for code 404

// a Request object is returned, just with the normal
// get/put/post/etc functions from this package. Hence, you can call
// all helper functinos as wanted. E.g.:
      .auth(user);

} catch (e) { // If e is not false, then, no error-catcher caught the error and // you might want to take care of it e && alert(e); } #+END_SRC

4.1.0

2 years ago

3.4.0

2 years ago

3.2.2

2 years ago

3.3.0

2 years ago

3.2.1

2 years ago

3.2.0

2 years ago

3.4.4

2 years ago

3.4.3

2 years ago

3.4.2

2 years ago

3.4.1

2 years ago

4.0.0

2 years ago

3.1.1

3 years ago

3.1.0

4 years ago

3.0.0

4 years ago