Based on @DavidWells shared JS Proxy tweets and GitHub gist.
Last week, I discovered an elegant way to create simple javascript REST API clients using Proxy.
Javascript Proxy ?
Proxy object is well documented in the Mozilla MDN documentation : https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy
First API Client with JS Proxy
Let’s create our first client API.
In this example, we will use the JSONPlaceholder API (https://jsonplaceholder.typicode.com/).
Client :
function createClient (url) {
return new Proxy({}, {
get(_, key) {
return async (id = "") => {
const reqUrl = new URL(`${key}/${id}`, url);
return fetch(reqUrl.toString()).then((d) => d.json());
}
}
})
}
Usage :
// Create client instance
const client = createClient('https://jsonplaceholder.typicode.com');
// Call /todos endpoint
await client.todos();
// Get todo element /todos/<id>
await client.todos(1);
Explainations :
In this example, we have created a JS Proxy on an empty object.
We have also declared the get
proxy method which contains the API Client logic.
When calling get(_, key)
, the key
parameter contains the name of the resource to call.
Ex: on client.todos
call, key
parameter contains 'todos'
.
To handle GET /resource/<id>
calls, the get
function return an asynchronous function with an id
parameter.
That’s why you can do client.todos(<id>)
.
The corresponding API endpoint is called on this asynchronous function and the result is returned.
Advanced usage
With the same logic, I tried to implement a Gitlab API client.
https://gitlab.com/ziggornif/gitlab-client
Problems
The previous example only work with GET calls on first resources level.
Gitlab API has multiple resources levels (ex: https://gitlab.com/api/v4/users/00000/projects)
The API has POST/PUT endpoints.
Solution
GET POST PUT requests
To manage the different methods (GET, POST, PUT …), I created a processRequest
function like the following.
function processRequest({url, method, headers, params, data}) {
const query = { headers, method }
const reqUrl = new URL(url)
switch (method) {
case 'GET':
if (params) {
for(const key of Object.keys(params)) {
reqUrl.searchParams.set(key, params[key])
}
}
break;
case "POST":
case "PUT":
case "PATCH": {
query.body = JSON.stringify(data);
break;
}
default:
break;
}
return fetch(reqUrl.toString(), query).then((d) => d.json());
}
This function handles all kinds of requests with body and / or query params.
Multiple resources levels calls (/resources//action)
To handle the multiple resources levels, I refactored the get
Proxy method.
Now, the request is sent if key
value is equal to an http verb (GET, POST, PUT…).
Otherwise, the get
method concatenate the URL with the current key and return a new Proxy.
function createClient (url) {
return new Proxy({}, {
get(_, key) {
const method = key.toUpperCase();
if (["GET", "POST", "PUT", "PATCH"].includes(method)) {
return async function({ data, params, headers } = {}) {
return processRequest({ url, method, data, params, headers })
}
}
return createClient(`${url}/${key}`, token);
}
})
}
With this solution, we can execute now more complex queries.
Example
// Get user projects
// https://gitlab.com/api/v4/users/00000/projects
const userProjs = await client.users[00000].projects.get();
console.log(userProjs);
Conclusion
JavaScript proxies can be really useful for creating API client libraries.
In addition, by writing these libraries in native JavaScript, they will be usable both on the backend side and on the frontend side.
Project example
You can retrieve and fork the complete project here : https://gitlab.com/ziggornif/gitlab-client