JavaScript · API · Advanced
API Client Class
Wrap an API in a reusable class for clean, maintainable fetch-based code.
Quick topic start and explanations before exercises (exercises below):
Building APIClient step by step
#Session and client class reference
#Exercises:
Basic APIClient with login
#Create an `APIClient` class with a `constructor` that sets `this.baseUrl` and `this.token = null`, and a `login(username, password)` method that fetches the token and stores it. Test it by calling `login()` and printing `client.token` (first 20 chars).
class APIClient {
constructor() {
// set baseUrl and token
}
async login(username, password) {
// fetch token from /api/auth/token/ and store in this.token
}
}
async function main() {
const client = new APIClient();
await client.login('YOUR_USERNAME', 'YOUR_PASSWORD');
console.log(client.token.slice(0, 20));
}
main();
Solution
class APIClient {
constructor() {
this.baseUrl = 'https://apilearn.tukas.dev';
this.token = null;
}
async login(username, password) {
const data = await fetch(`${this.baseUrl}/api/auth/token/`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, password }),
}).then(r => r.json());
this.token = data.token;
}
}
async function main() {
const client = new APIClient();
await client.login('YOUR_USERNAME', 'YOUR_PASSWORD');
console.log(client.token.slice(0, 20));
}
main();
Add a _fetch() helper
#Add a `_fetch(path, options = {})` method that builds auth headers automatically and calls `fetch`. Then add `getProfile()` that uses `_fetch`. Test both.
class APIClient {
constructor() {
this.baseUrl = 'https://apilearn.tukas.dev';
this.token = null;
}
async login(username, password) {
const data = await fetch(`${this.baseUrl}/api/auth/token/`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, password }),
}).then(r => r.json());
this.token = data.token;
}
async _fetch(path, options = {}) {
// build headers with auth, call fetch
}
async getProfile() {
// use _fetch
}
}
async function main() {
const client = new APIClient();
await client.login('YOUR_USERNAME', 'YOUR_PASSWORD');
const profile = await client.getProfile();
console.log(profile.username);
}
main();
Solution
class APIClient {
constructor() {
this.baseUrl = 'https://apilearn.tukas.dev';
this.token = null;
}
async login(username, password) {
const data = await fetch(`${this.baseUrl}/api/auth/token/`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, password }),
}).then(r => r.json());
this.token = data.token;
}
async _fetch(path, options = {}) {
const headers = {
'Content-Type': 'application/json',
...(this.token ? { 'Authorization': `Token ${this.token}` } : {}),
...options.headers,
};
return fetch(`${this.baseUrl}${path}`, { ...options, headers });
}
async getProfile() {
return this._fetch('/api/users/profile/').then(r => r.json());
}
}
async function main() {
const client = new APIClient();
await client.login('YOUR_USERNAME', 'YOUR_PASSWORD');
const profile = await client.getProfile();
console.log(profile.username);
}
main();
Add getProducts()
#Add a `getProducts(params = {})` method that accepts a params object and returns the `results` array. Test it with no params and with `{ category: "chairs" }`.
class APIClient {
constructor() {
this.baseUrl = 'https://apilearn.tukas.dev';
this.token = null;
}
async login(username, password) {
const data = await fetch(`${this.baseUrl}/api/auth/token/`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, password }),
}).then(r => r.json());
this.token = data.token;
}
async _fetch(path, options = {}) {
const headers = {
'Content-Type': 'application/json',
...(this.token ? { 'Authorization': `Token ${this.token}` } : {}),
...options.headers,
};
return fetch(`${this.baseUrl}${path}`, { ...options, headers });
}
async getProducts(params = {}) {
// use URLSearchParams and _fetch, return results
}
}
async function main() {
const client = new APIClient();
await client.login('YOUR_USERNAME', 'YOUR_PASSWORD');
console.log((await client.getProducts()).length);
console.log((await client.getProducts({ category: 'chairs' })).length);
}
main();
Solution
class APIClient {
constructor() {
this.baseUrl = 'https://apilearn.tukas.dev';
this.token = null;
}
async login(username, password) {
const data = await fetch(`${this.baseUrl}/api/auth/token/`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, password }),
}).then(r => r.json());
this.token = data.token;
}
async _fetch(path, options = {}) {
const headers = {
'Content-Type': 'application/json',
...(this.token ? { 'Authorization': `Token ${this.token}` } : {}),
...options.headers,
};
return fetch(`${this.baseUrl}${path}`, { ...options, headers });
}
async getProducts(params = {}) {
const qs = new URLSearchParams(params);
return this._fetch(`/api/products/?${qs}`).then(r => r.json()).then(d => d.results);
}
}
async function main() {
const client = new APIClient();
await client.login('YOUR_USERNAME', 'YOUR_PASSWORD');
console.log((await client.getProducts()).length);
console.log((await client.getProducts({ category: 'chairs' })).length);
}
main();
Full APIClient
#Build a complete APIClient with: `login()`, `_fetch()`, `getProducts(params)`, `getProfile()`, `addToCart(slug, qty)`, `clearCart()`. Then use all methods in sequence.
class APIClient {
// implement all methods
}
async function main() {
const client = new APIClient();
await client.login('YOUR_USERNAME', 'YOUR_PASSWORD');
const products = await client.getProducts();
console.log('Products:', products.length);
const item = await client.addToCart(products[0].slug);
console.log('Cart item id:', item.id);
const profile = await client.getProfile();
console.log('User:', profile.username);
await client.clearCart();
console.log('Cart cleared');
}
main();
Solution
class APIClient {
constructor() {
this.baseUrl = 'https://apilearn.tukas.dev';
this.token = null;
}
async _fetch(path, options = {}) {
const headers = {
'Content-Type': 'application/json',
...(this.token ? { 'Authorization': `Token ${this.token}` } : {}),
...options.headers,
};
return fetch(`${this.baseUrl}${path}`, { ...options, headers });
}
async login(username, password) {
const data = await this._fetch('/api/auth/token/', {
method: 'POST',
body: JSON.stringify({ username, password }),
}).then(r => r.json());
this.token = data.token;
}
async getProducts(params = {}) {
const qs = new URLSearchParams(params);
return this._fetch(`/api/products/?${qs}`).then(r => r.json()).then(d => d.results);
}
async getProfile() {
return this._fetch('/api/users/profile/').then(r => r.json());
}
async addToCart(productSlug, quantity = 1) {
return this._fetch('/api/cart/items/', {
method: 'POST',
body: JSON.stringify({ product_slug: productSlug, quantity }),
}).then(r => r.json());
}
async clearCart() {
await this._fetch('/api/cart/items/', { method: 'DELETE' });
}
}
async function main() {
const client = new APIClient();
await client.login('YOUR_USERNAME', 'YOUR_PASSWORD');
const products = await client.getProducts();
console.log('Products:', products.length);
const item = await client.addToCart(products[0].slug);
console.log('Cart item id:', item.id);
const profile = await client.getProfile();
console.log('User:', profile.username);
await client.clearCart();
console.log('Cart cleared');
}
main();
Add placeOrder() method
#Extend APIClient with a `placeOrder(phoneNumber)` method. Then use the client to: login, add a product, place an order, and print the order `id` and `status`.
class APIClient {
constructor() {
this.baseUrl = 'https://apilearn.tukas.dev';
this.token = null;
}
async _fetch(path, options = {}) {
const headers = {
'Content-Type': 'application/json',
...(this.token ? { 'Authorization': `Token ${this.token}` } : {}),
...options.headers,
};
return fetch(`${this.baseUrl}${path}`, { ...options, headers });
}
async login(username, password) {
const data = await this._fetch('/api/auth/token/', {
method: 'POST',
body: JSON.stringify({ username, password }),
}).then(r => r.json());
this.token = data.token;
}
async getProducts(params = {}) {
const qs = new URLSearchParams(params);
return this._fetch(`/api/products/?${qs}`).then(r => r.json()).then(d => d.results);
}
async addToCart(productSlug, quantity = 1) {
return this._fetch('/api/cart/items/', {
method: 'POST',
body: JSON.stringify({ product_slug: productSlug, quantity }),
}).then(r => r.json());
}
async placeOrder(phoneNumber) {
// POST /api/orders/ and return result
}
}
async function main() {
const client = new APIClient();
await client.login('YOUR_USERNAME', 'YOUR_PASSWORD');
const products = await client.getProducts();
await client.addToCart(products[0].slug);
const order = await client.placeOrder('+1234567890');
console.log(order.id);
console.log(order.status);
}
main();
Solution
class APIClient {
constructor() {
this.baseUrl = 'https://apilearn.tukas.dev';
this.token = null;
}
async _fetch(path, options = {}) {
const headers = {
'Content-Type': 'application/json',
...(this.token ? { 'Authorization': `Token ${this.token}` } : {}),
...options.headers,
};
return fetch(`${this.baseUrl}${path}`, { ...options, headers });
}
async login(username, password) {
const data = await this._fetch('/api/auth/token/', {
method: 'POST',
body: JSON.stringify({ username, password }),
}).then(r => r.json());
this.token = data.token;
}
async getProducts(params = {}) {
const qs = new URLSearchParams(params);
return this._fetch(`/api/products/?${qs}`).then(r => r.json()).then(d => d.results);
}
async addToCart(productSlug, quantity = 1) {
return this._fetch('/api/cart/items/', {
method: 'POST',
body: JSON.stringify({ product_slug: productSlug, quantity }),
}).then(r => r.json());
}
async placeOrder(phoneNumber) {
return this._fetch('/api/orders/', {
method: 'POST',
body: JSON.stringify({ phone_number: phoneNumber }),
}).then(r => r.json());
}
}
async function main() {
const client = new APIClient();
await client.login('YOUR_USERNAME', 'YOUR_PASSWORD');
const products = await client.getProducts();
await client.addToCart(products[0].slug);
const order = await client.placeOrder('+1234567890');
console.log(order.id);
console.log(order.status);
}
main();