Human Readable AJAX Requests
3 min read

Human Readable AJAX Requests

As a JavaScript developer, you must have seen code like this before:

fetch('/users.json', {
    method: 'PATCH',
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      name: 'Hubot',
      login: 'hubot',
    }),
  })
  .then(response => response.json())
  .then(json => console.log('parsed json', json))
  .catch(ex => console.log('parsing failed', ex))

Anyone who claims to be a web developer knows that fetch is the new standard for making http requests from JS. Last week, I came up with an idea to wrap this api in something that is easily readable and sharable. What if we could turn the code above into text form?

If you inspect any page in Chrome, you can switch to the network tab and view the request headers, url, etc. This is shown under the general tab:

Request URL:http://test.app/settings/user/19
Request Method:PATCH
Status Code:200 OK
Remote Address:192.168.10.10:80

If you scroll down, there's a request payload section showing the body of the request, if there is one.

Legible

Anyone familiar with HTTP requests knows what each line in chrome's network tab means. It's too bad we can't copy and paste this into our code and do that exact request..... But we can (almost), if my idea pans out! Take a look at what I'm proposing:

request`
  url: http://test.app/settings/user/19
  method: PATCH
  headers: ${{ 
    Authorization: 'Bearer: token' 
  }}
  body: ${{ name: 'Bob' }}
`

I call it legible and it's on my github right now!

It was made in a couple hours, the night before posting this. I'm really happy with the code quality, minus one thing. For you to understand, you'll need a better understanding of Template Literals. Take a look at Max's blog post on the magic behind styled-components. I thought up this library after reading his post.

How's it work?

That request function looks like this at its core:

const request = (strings, ...vars) => {
  
}
request`hey there ${'blah'}`

Strings is an array of strings, each variable (this part: ${}) is passed as another argument to the function. We can use the ... spread operator to put them all in an array. Read the two links above on template literals for more information.

The gist is this, we loop through each string looking for the piece we want, such as url or method and then return that value. The string is then made into a fetch options object, which looks like this:

{ 
  method: 'GET',
  headers: {},
  body: JSON.stringify({})
}

The hard part is grabbing the body and headers variable from the correct line.

request`
  body: ${{}}
  headers: ${{}}
`

There's sadly not an easy way with the current api to find what line a variable is on. For now, I am simply finding the line index of body and headers, and grabbing the first or second variable that is passed in. This is the only code smell of the library so far. I would love for someone smarter than me to help me solve this better. Now back to why I made this...

At first I thought this was crazy and pointless, who would want plain text in their code!? After sending the idea to a couple friends, Ken Wheeler and Max Stoiber, they actually thought it was worth pursuing. After showing them two more extensions of this idea (that I will cover below) they seemed sold on the idea.

Without going further, let's just take a second and look at the biggest upside of this approach: It's easy for anyone to understand. With a basic understanding of how requests work, you can write any request. It's so natural to anyone that you aren't learning anything new by switching to it.

Documenting API's

If this library takes off, I'm proposing this for abstracting out api logic:

import request, { requestable } from 'legible'

const twitter = { 
  register: requestable`
    url: 'https://api.twitter.com/register', 
    headers: ${{ 
      method: 'POST' 
    }} 
  `,
  tweets: requestable`
    url: https://api.twitter.com/tweets, 
  `,
 }

request.attach('twitter', twitter)

async function register() {
  let response = await request.twitter.register`
    body: ${{ 
      email: 'test@test.com', 
      password: 'Tester' 
    }}
  `
}

If you couldn't deduce from the code above, the methods you attach to the request get merged when called. So you can specify a method, such as register and when you call it later, only specify the body of the request for example. I could see people releasing api libraries that include routes, headers, and other things needed to make requests.

Middleware

The last thing I want this library to accomplish is a simple middleware stack. One thing I'm doing constantly is making authenticated requests, and I need to update the client with a new token on every response. Something like this is what I'm imagining.

import request from 'legible'

request.middleware({
  headers: {
    Authorization: `Bearer: ${localStorage.getItem('token')}`
  },
  after({ headers }) {
    localStorage.setItem('token', headers.Authorization)
  }
})

Wrap up

If you think the ideas above are interesting or useful, feel free to go checkout the code for legible on my github. It's in the very early stages. You can make requests, but the attach and middleware methods are not yet implemented. I'm not even sure the above examples are exactly how I want them implemented either!

I wrote this post to see if anyone else thought this could be a useful approach to interacting with api's. So please leave feedback here or tweet me. As always, don't forget to subscribe to my blog for more interesting posts :)

Enjoying these posts? Subscribe for more