@apparts/api
v4.1.0
Published
A unified API package for @apparts
Downloads
13
Readme
#+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 =urlParams[0]=, =$2= with =urlParams[1]=, 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.
- Status parameter:
- =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