How do you stucture front-end API calls?

In which I ponder about how to structure frontend API calls in JavaScript & TypeScript. Object oriented? Procedural? Functional?

Dec. 10, 2017

Just a disclaimer: I don't offer any strong opinions or solutions here. I'm just writing down some internal thoughts I had about how to structure API calls from a front end to an API.


So just yesterday I had to implement some functionality that most/all front-end web or application developers have to do. After a user interaction make a REST API call over HTTP to an API server.

For whatever reason I struggled with this particular one, even though I have done it hundreds of times now. Not because I didn't know how but because there were many ways to implement it and I wasn't (and still am not) sure which was best suited for my use case. Just for some background I am working in React/Redux & using axios as my HTTP library (a fairly common setup).

After thinking about this problem I've vaguely characterized three different approaches that I categorized in terms of three "dogmas" of programming: procedural, functional, and object oriented.

Here is the hypothetical scenario: I have an image that I have saved in a database with some meta information. One of those pieces of information is whether or not that image is "visible". What that means or how it's used isn't necessary for this exercise.

Procedural

This method is the most obvious but I find myself using it less and less these days: making the api call in the React component itself when it needs to be called. We make the api call here and bind both the initial state and changed state to the state of the image in the backend.

import React, { Component } from 'react';
import axios from 'axios';

class ImageToggle extends Component {
  constructor(props) {
    super(props);
    this.state = {
      showImage: props.image.visible,
    };
    this.handleToggleVisibility = this.handleToggleVisibility.bind(this);
  }
  
  handleToggleVisibility(event) {
    event.preventDefault();
    axios.patch(`/images/${this.props.image.id}/`, { visible: !this.state.visible })
    .then(response => {
      const image = response.data;
      this.setState({ visible: image.visible });
    })
  }
  
  render() {
    return (
      <button onClick={this.handleToggleVisibility}>
        {this.props.image.visible ? 'Visible!' : 'Invisible'}
      </button>
    );
  }
  
}

export default ImageToggle;

Functional

The problem with the above is what if I need to make this API call in a different place? Should I duplicate this logic (violating DRY) there? A more conventional React/Redux implementation may be more appropriate. In this example the `Image` is stored inside our redux store and we update it by firing an action:

import React, { Component } from 'react';
import { connect } from 'react-redux';

import { updateImage } from '../actions/images';

class ImageToggle extends Component {
  constructor(props) {
    super(props);
    this.state = {
      showImage: props.image.visible,
    };
    this.handleToggleVisibility = this.handleToggleVisibility.bind(this);
  }
  
  handleToggleVisibility(event) {
    event.preventDefault(); 
    this.props.(this.props.image.id, { visible: !this.props.image.visible });
  }
  
  render() {
    return (
      <button onClick={this.handleToggleVisibility}>
        {this.props.image.visible ? 'Visible!' : 'Invisible'}
      </button>
    );
  }
}

export default connect(null, { updateImage })(ImageToggle);

And then in the action function with the redux-thunk middleware installed:

export const updateImage = (id, imageData) => {
  return dispatch => {
    axios.patch(`/images/${id}/`, imageData)
    .then(response => {
      const image = response.data;
      dispatch({ type: 'IMAGE_UPDATED', payload: image });
    })
  }
}

Which will update my reducer store, my component is re-rendered with the new image and the same effect is achieved as the "procedural" version, but now I can re-use this action in other parts of my application. The caveat here is that it requires having redux and the thunk (or other redux tools like redux-saga) middleware installed. Not an option on apps that don't make use of those libraries.

Object Oriented

This third solution crossed my mind as I was working in Django using the ORM. In Django/python I would do something like:

class Image(models.Model):
    url = models.URLField(blank=False, null=False)
    visible = models.BooleanField(default=True)
 
    def toggle_visibility(self):
        self.visible = not self.visible
        self.save()

Nothing stops me from making image into a JavaScript class and implementing methods that have similar functionality:

class Image {
  constructor(id, url, visible) {
    this.id = id;
    this.url = url;
    this.visible = visible;
  }

  toggleVisiblity() {
    return this.save({ id: this.id, url: this.url, visible: !this.visible });
  }
  
  save(data) {
    return axios.put(`/images/${this.id}/`, data);
  }
}

and then in our component we replace the handleToggleVisibility method in our procedural version:

...

  handleToggleVisibility(event) {
    event.preventDefault();
    this.props.image.toggleVsibility().then(response => {
      const image = response.data;
      this.setState({ visible: image.visible });
    })
  }

  ...

This example may be useful if you are already using a lot of object oriented approaches, or maybe using the Mobx state management library in favor of Redux.

As usual in software development I'm not sure there are is a "best" option here. Ultimately we want to bind our UI to the state of the image from the database, the method we choose to get there depends on a lot of decisions that this hypothetical doesn't cover about the architecture of our application. Would be interested to see if there are any other "styles" of front-end API calls out there!