JavaScript · API · Advanced

API Client Class

5 tasks

Wrap an API in a reusable class for clean, maintainable fetch-based code.

Why wrap an API in a class

#
Writing raw `fetch` calls everywhere quickly becomes repetitive: every request needs the base URL, auth headers, and `Content-Type`. When the API changes, you have to update many places. Wrapping the API in a class solves this: ```javascript const client = new APIClient(); await client.login('alice42', 'SecurePass123!'); const products = await client.getProducts({ category: 'chairs' }); await client.addToCart('oak-chair', 2); ``` The class: - stores the base URL and auth token internally - builds headers once and reuses them - exposes meaningful method names (`login`, `getProducts`) instead of raw HTTP details The core pattern — a helper method that adds auth headers automatically: ```javascript 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, }; const response = await fetch(`${this.baseUrl}${path}`, { ...options, headers }); return response; } } ``` Every public method calls `this._fetch()`, so auth is handled in one place.

Building APIClient step by step

#
Full APIClient with the most useful methods: ```javascript 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, }; const response = await fetch(`${this.baseUrl}${path}`, { ...options, headers }); return response; } 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' }); } } // Usage async function main() { const client = new APIClient(); await client.login('YOUR_USERNAME', 'YOUR_PASSWORD'); const products = await client.getProducts({ category: 'chairs' }); console.log(`Found ${products.length} chairs`); const item = await client.addToCart(products[0].slug, 2); console.log('Cart item id:', item.id); const profile = await client.getProfile(); console.log('Logged in as:', profile.username); await client.clearCart(); } main(); ```

Session and client class reference

#
**Class skeleton checklist** ```javascript class APIClient { constructor() { this.baseUrl = '...'; // set once this.token = null; // set after login } async _fetch(path, options) { /* auth + fetch */ } async login(user, pass) { /* sets this.token */ } async getProducts(params) { /* calls _fetch */ } } ``` **_fetch helper pattern** ```javascript async _fetch(path, options = {}) { const headers = { 'Content-Type': 'application/json', ...(this.token ? { 'Authorization': `Token ${this.token}` } : {}), ...options.headers, // allow per-call overrides }; return fetch(`${this.baseUrl}${path}`, { ...options, headers }); } ``` **Spread merge order matters** ```javascript // Later keys override earlier keys { ...defaults, ...perRequest } // perRequest.Authorization overrides defaults.Authorization if present ``` **Per-method pattern** ```javascript // Read — no body async getProfile() { return this._fetch('/api/users/profile/').then(r => r.json()); } // Write — with body async addToCart(slug, quantity = 1) { return this._fetch('/api/cart/items/', { method: 'POST', body: JSON.stringify({ product_slug: slug, quantity }), }).then(r => r.json()); } // Delete — no response body (204) async clearCart() { await this._fetch('/api/cart/items/', { method: 'DELETE' }); } ```
01

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();
02

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();
03

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();
04

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();
05

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();