<div class="container mt-3">
  <header class="clearfix">
    <h1 class='d-inline-block'>Fetch Example</h1>
    <a href="https://time2hack.com/2017/11/goodbye-xmlhttprequest-ajax-with-fetch-api-demo/" target="_blank" class="btn btn-info float-right">Read full post</a>
  </header>
  <hr/>
  <div class="row">
  <div class="col-sm-8">
    <div class="row">
      <div class="col-sm-12"  data-param="false">
        <div class="form-group">
          <label for="endpoint">Endpoint</label>
          <input type="text"  value="https://jsonplaceholder.typicode.com/posts" class="form-control" id="endpoint" >
        </div>
      </div>        
      <div data-param="true" class="col-sm-12 d-none">
        <div class="row">
          <div class="col-sm-9">
            <div class="form-group">
              <label for="endpoint">Endpoint</label>
              <input type="text" readonly value="https://jsonplaceholder.typicode.com/posts" class="form-control" id="endpoint" >
            </div>
          </div>
          <div class="col-sm-3">
            <div class="form-group">
              <label for="param">Param (postId)</label>
              <input type="text" readonly value="10" class="form-control" id="param" >
            </div>
          </div>        
        </div>
      </div>
    </div>
  </div>
  <div class="col-sm-4">
    <div class="form-group">
      <label for="method">Method</label>
      <select class="form-control" id="method">
        <option value="GET">GET</option>
        <option value="POST" data-body selected>POST</option>
        <option value="PUT" data-body data-param>PUT</option>
        <option value="DELETE" data-param>DELETE</option>
      </select>
    </div>  
  </div>
  </div>
  <div class="row">
    <div class="col-md-6"></div>
    <div class="col-md-6"></div>
  </div>
  <div class="form-group fields-group">
    <label >Fields: <button type="button" id="add" class="btn btn-info btn-sm">Add Field</button></label>
    <table id="fields" class="table table-bordered table-sm">
      <thead>
        <tr>
          <th>Key</th>
          <th>Value</th>
          <th>Remove</th>
        </tr>
      </thead>
      <tbody>
        <tr>
          <td><input type="text" class="form-control key" placeholder="Key"></td>
          <td><input type="text" class="form-control value" placeholder="Value" /></td>
          <td>
            <button type="button" class="btn btn-outline-danger remove">Remove</button>
          </td>
        </tr>
      </tbody>
    </table>
    <small class="form-text text-muted">Empty keys won't be sent</small>
  </div>
  <div>
    <button type="button" class="btn btn-outline-primary btn-lg" id="send">Send</button>
    <span class="d-none" id="status">
      <svg width="24" height="24" viewBox="0 0 24 24">
  <path d="M19,8L15,12H18A6,6 0 0,1 12,18C11,18 10.03,17.75 9.2,17.3L7.74,18.76C8.97,19.54 10.43,20 12,20A8,8 0 0,0 20,12H23M6,12A6,6 0 0,1 12,6C13,6 13.97,6.25 14.8,6.7L16.26,5.24C15.03,4.46 13.57,4 12,4A8,8 0 0,0 4,12H1L5,16L9,12"></path>
</svg>
    </span>
  </div>
  <hr/>
  <div class="response">
    <div class="row">
      <div class="col-sm-8">
        <h4>Response:</h4>
        <pre id="result" class="border border-primary rounded"></pre>        
      </div>
      <div class="col-sm-4">
        <h4>Request:</h4>
        <pre id="request" class="border border-primary rounded"></pre>
      </div>
    </div>
  </div>
</div>
#result, #request {
  padding: 10px;
  max-height: 500px;
  overflow: auto;
}
header {
  padding-top: 10px; 
}
#status svg{
    animation: spin infinite 2s linear;
}
@keyframes spin {
  from {
    transform: rotate(360deg);
  }
  to {
    transform: rotate(0deg);
  }
}
const input = name => `<input type="text"
  class="form-control ${name.toLowerCase()}" placeholder="${name}" />`;

const fieldTpl = data => `
<tr>
  <td>${input("Key")}</td>
  <td>${input("Value")}</td>
  <td><button type="button" class="btn btn-outline-danger remove">Remove</button></td>
</tr>
`;
document.querySelector("#add").addEventListener("click", e => {
  $append(fieldTpl(), document.querySelector("#fields tbody"));
});

document.querySelector("#method").addEventListener("change", e => {
  const val = document.querySelector("#method").value;
  const option = document.querySelector(`#method option[value="${val}"]`);
  const param = option.dataset.param !== undefined;
  
  document
    .querySelector(".fields-group")
    .classList[option.dataset.body === undefined ? "add" : "remove"]("d-none");
  console.log(param, document.querySelector(`[data-param='${param}']`));
  document.querySelector(`[data-param='${param}']`).classList.remove('d-none')
  document.querySelector(`[data-param='${!param}']`).classList.add('d-none')
});

document.querySelector("#fields").addEventListener("click", e => {
  if (e.target.classList.contains("remove")) {
    e.target.parentElement.parentElement.remove();
  }
});

const getKeyValues = () => {
  const keys = document.querySelectorAll('.key');
  const values = document.querySelectorAll('.value');
  let obj = {};
  keys.forEach((key, index) => {
    if(key.value !== '') {
      obj[key.value] = values[index].value;
    }
  });
  return obj;
}

document.querySelector("#send").addEventListener("click", e => {
  document.querySelector("#send").classList.add('d-none')
  document.querySelector("#status").classList.remove('d-none')
  const suffix = {
    GET: '',
    POST: '',
    PUT: '/10',
    DELETE: '/10',
  };
  const method = document.querySelector("#method").value;
  const url = `https://jsonplaceholder.typicode.com/posts${suffix[method]}`;
  let reqConf = {method};
  if(method === 'POST' || method === 'PUT') {
    const headers = new Headers();
    headers.append("Content-Type", "application/json");
    let body = getKeyValues();
    
    reqConf = Object.assign({}, {
      headers, 
      body: JSON.stringify(body),
    }, reqConf);
  }
  const request = new Request(url, reqConf)
  document.querySelector('#request').innerHTML = JSON.stringify(request, replacer, 2);
  fetch(request)
    .then(response => response.json())
    .then(data => {
      document.querySelector("#send").classList.remove('d-none')
      document.querySelector("#status").classList.add('d-none')
      document.querySelector('#result').innerHTML = JSON.stringify(data, replacer, 2);
  });
});

const $append = (markup, parent) => {
  const temp_container = document.createElement('tbody');
  temp_container.innerHTML = markup;
  while (temp_container.firstChild) {
    parent.appendChild(temp_container.firstChild);
  }
};

function replacer(key, value) {
  // Filtering out properties
  if (value instanceof Request) {
    return ['method', 'url', 'headers', 'referrer', 'mode', 'body'].reduce((acc, key) => {
      acc[key] = replacer(key, value[key]);
      return acc;
    }, {});
  }
  if (value instanceof Headers) {
    const i = value.entries();
    const ret = {};
    let v = i.next();
    while(!v.done) {
      ret[v.value[0]] = v.value[1]
      v = i.next();
    }
    return ret;
  }
  return value;
}
View Compiled
Run Pen

External CSS

  1. https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0-beta/css/bootstrap.min.css

External JavaScript

This Pen doesn't use any external JavaScript resources.