<div class="content">
<h1>Simple API wrapper demo</h1>
<p>Read more <a href="https://stanko.github.io/simple-javascript-api-wrapper/" target="_blank">on my blog</a>.</p>
<div>
<button id="req-people">Get People</button>
<button id="req-person">Get Person</button>
<button id="req-404">404 error (with JSON response)</button>
<button id="req-json-error">Invalid JSON response</button>
</div>
<pre id="console"></pre>
<div class="note">
For demo purposes I'm using awesome <a href="https://swapi.co">Star Wars API</a>
</div>
</div>
body {
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
line-height: 1.2em;
font-size: 16px;
padding: 40px 20px;
color: #24292e;
}
h1 {
font-size: 2em;
margin-bottom: 10px;
}
p {
margin-bottom: 40px;
}
.content {
max-width: 1000px;
position: relative;
margin: 0 auto;
}
pre {
margin-top: 10px;
overflow-x: auto;
font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace;
line-height: 1.3em;
padding: 20px;
border: 1px solid #ddd;
border-radius: 3px;
background: #f6f8fa;
div:not(:last-child) {
border-bottom: 1px solid #ddd;
padding-bottom: 20px;
margin-bottom: 20px;
}
}
button {
margin-right: 5px;
margin-bottom: 5px;
border: 1px solid #ddd;
background: #fff;
font-size: 14px;
border-radius: 50px;
padding: 5px 12px;
transition: all 250ms;
outline: none;
&:hover:not(:disabled) {
color: #2980B9;
border-color: #2980B9;
cursor: pointer;
}
&:disabled {
opacity: 0.5;
}
}
.note {
margin-top: 40px;
color: #ccc;
font-size: 14px;
a {
color: #ccc;
}
}
View Compiled
// ------------------------------------------------------ //
// Simple JavaScript API wrapper
// https://stanko.github.io/simple-javascript-api-wrapper
// ------------------------------------------------------ //
// For demo purposes I'm using this awesome Star Wars API
const API_URL = 'https://swapi.co/api';
// Custom API error to throw
function ApiError(message, data, status) {
let response = null;
let isObject = false;
// We are trying to parse response
try {
response = JSON.parse(data);
isObject = true;
} catch (e) {
response = data;
}
this.response = response;
this.message = message;
this.status = status;
this.toString = function () {
return `${ this.message }\nResponse:\n${ isObject ? JSON.stringify(this.response, null, 2) : this.response }`;
};
}
// API wrapper function
const fetchResource = (path, userOptions = {}) => {
// Define default options
const defaultOptions = {};
// Define default headers
const defaultHeaders = {};
const options = {
// Merge options
...defaultOptions,
...userOptions,
// Merge headers
headers: {
...defaultHeaders,
...userOptions.headers,
},
};
// Build Url
const url = `${ API_URL }/${ path }`;
// Detect is we are uploading a file
const isFile = options.body instanceof File;
// Stringify JSON data
// If body is not a file
if (options.body && typeof options.body === 'object' && !isFile) {
options.body = JSON.stringify(options.body);
}
// Variable which will be used for storing response
let response = null;
return fetch(url, options)
.then(responseObject => {
// Saving response for later use in lower scopes
response = responseObject;
// HTTP unauthorized
if (response.status === 401) {
// Handle unauthorized requests
// Maybe redirect to login page?
}
// Check for error HTTP error codes
if (response.status < 200 || response.status >= 300) {
// Get response as text
return response.text();
}
// Get response as json
return response.json();
})
// "parsedResponse" will be either text or javascript object depending if
// "response.text()" or "response.json()" got called in the upper scope
.then(parsedResponse => {
// Check for HTTP error codes
if (response.status < 200 || response.status >= 300) {
// Throw error
throw parsedResponse;
}
// Request succeeded
return parsedResponse;
})
.catch(error => {
// Throw custom API error
// If response exists it means HTTP error occured
if (response) {
throw new ApiError(`Request failed with status ${ response.status }.`, error, response.status);
} else {
throw new ApiError(error, null, 'REQUEST_FAILED');
}
});
};
// ------------------------------------------------------ //
// DEMO
// PLEASE NOTE:
// this is a very naive implementation for demo purposes
// ------------------------------------------------------ //
// Define API calls
const getPeople = () => {
return fetchResource('people');
};
const getPerson = (personId) => {
return fetchResource(`people/${ personId }`);
};
const getJsonError = () => {
return fetchResource('not-found');
};
// Get dom nodes
const consoleElement = document.querySelector('#console');
const buttonPeople = document.querySelector('#req-people');
const buttonPerson = document.querySelector('#req-person');
const button404 = document.querySelector('#req-404');
const buttonJsonError = document.querySelector('#req-json-error');
// Create "actions"
function requestPeople() {
// Save button text and set it to loading
const buttonText = this.innerHTML;
this.innerHTML = 'Loading...';
getPeople()
.then(data => {
consoleElement.innerHTML = `<div>${ JSON.stringify(data, null, 2) }</div>${ consoleElement.innerHTML }`;
// Reset button text
this.innerHTML = buttonText;
})
.catch(error => {
consoleElement.innerHTML = `<div>${ error }</div>${ consoleElement.innerHTML }`;
// Reset button text
this.innerHTML = buttonText;
});
}
function requestPerson() {
// Save button text and set it to loading
const buttonText = this.innerHTML;
this.innerHTML = 'Loading...';
getPerson(1)
.then(data => {
consoleElement.innerHTML = `<div>${ JSON.stringify(data, null, 2) }</div>${ consoleElement.innerHTML }`;
// Reset button text
this.innerHTML = buttonText;
})
.catch(error => {
consoleElement.innerHTML = `<div>${ error }</div>${ consoleElement.innerHTML }`;
// Reset button text
this.innerHTML = buttonText;
});
}
function request404() {
// Save button text and set it to loading
const buttonText = this.innerHTML;
this.innerHTML = 'Loading...';
getPerson('not-found')
.then(() => {
// Skipping as it will always fail
})
.catch(error => {
consoleElement.innerHTML = `<div>${ error }</div>${ consoleElement.innerHTML }`;
// Reset button text
this.innerHTML = buttonText;
});
}
function requestJsonError() {
// Save button text and set it to loading
const buttonText = this.innerHTML;
this.innerHTML = 'Loading...';
getJsonError()
.then(() => {
// Skipping as it will always fail
})
.catch(error => {
// Escaping HTML
const errorContent = document.createElement('div');
errorContent.innerText = error;
consoleElement.innerHTML = `<div>${ errorContent.innerHTML }</div>${ consoleElement.innerHTML }`;
// Reset button text
this.innerHTML = buttonText;
});
}
// Bind actions to buttons
buttonPeople.addEventListener('click', requestPeople);
buttonPerson.addEventListener('click', requestPerson);
button404.addEventListener('click', request404);
buttonJsonError.addEventListener('click', requestJsonError);
View Compiled
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.