3.13 Fetching Data
Hello
Zen and the art of Coding
Please email hello@zenandtheartofcoding.info for a password

Fetching Data

📚 Table of Contents
API 101
HTTP Methods
fetch()
  • now that we know what promises are, we can start to do some really cool shit
  • I'm going to step on the gas a little here. I'm still going to try and spell things out simply but we are also going to pick up the pace
  • at their core, most modern apps, websites and SaaS platforms perform CRUD (Create Read Update Delete) operations from a UI (frontend) on a database (backend) via an API (Application Programming Interface)

API 101

  • API's are the modern way frontends and backends communicate in web development
  • in this context, API's are usually accessible through a URL like a regular website, i.e. https://api.example.com
  • https://api.example.com would be considered the base URL. from there the backend devs will give specific paths which are known as endpoints, i.e. https://api.example.com/users, which would presumably give you access to the users in the database
  • the API endpoints will give you access to perform CRUD operations on the data and through HTTP protocalls

HTTP Methods

these are the CRUD methods that you can use

  • GET: gets your data so you can Read it
  • POST: sends or posts data so you can Create it
  • PUT: replaces data, or puts an Update through
  • DELETE: Deletes data

fetch()

  • fetch() is super important. it is the modern way to fetch resources asynchronously
    • fun fact: for context, the old way is known as AJAX (asynchronous JavaScript and XML).
  • when I say fetching resources, I mean it can work with local files, can include requests that involve cookies, can work with streaming responses, image and file uploads but most importantly it is used to hit API endpoints
  • fetch() is a method / function that returns a promise once the response is available
  • the basic syntax is:
fetch(resource, options);
  • the options are optional 🙃 (more on that in a minute)
  • the resource is the URL, so you can use fetch() like this:
fetch("https://api.example.com/users");
  • without options provided, fetch() will by default perform a GET request and download the resources from the URL
  • fetch() returns a response object. the response is important because obviously that is where the data that you need is going to be, but before you access the data, the response object has this cool feature where you can check the status of the response to make sure everything is working. you do this by literally checking response.ok
  • response.ok returns a boolean (true or false) if the request is good or not. good meaning it received an HTTP status code (opens in a new tab) in the 200's. you can check the specific status code too by checking response.status
  • if you have a good response, you use your data with response.json(). .json() on the response object will read the entire response, parse the body text as JSON and return a promise that resolves witht he result
  • ok that was a lot of explaining, I think it makes a little more sense actually coding this out:
async function fetchData() {
  const response = await fetch("https://api.example.com/users");
  if (response.ok) {
    const data = await response.json();
    console.log(data);
  }
}
  • that console.log() would show you the list of users that you just got back from the API endpoint. notice the await keyword before the promises
  • the if (response.ok) is also of course optional but its good practice to use it. actually, it is generally considered good practice to use a try / catch here to account for bad responses and errors. this is pretty mucht the same thing but even better (going to switch to an arrow function for style 😎):
const fetchUsers = async () => {
  try {
    const response = await fetch("https://api.example.com/users");
    if (!response.ok) {
      throw new Error("Failed to fetch users");
    }
    const data = await response.json();
    console.log(data);
  } catch (error) {
    console.error("Error:", error);
  }
};
  • with me so far? if the info above is a little hazy for you, we're going to see this in action with a real API in the next exercise, so sit tight. before we get to using a real API I want to show a few more things
  • lets mock a POST request to the same example API. POST requests send info to the API, so you would need to do this if you were creating a new user in the database
  • this is when we need to use the options in fetch()
  • options are passed in as an object, so we need to use curly braces ({}) and then define the data we are sending in key: value pairs. we also have to include the HTTP method, headers and the body
  • lets create a new function to create a user that will take in userData (maybe from a sign up form or something) and send that userData to the API via a POST request
const createUser = async (userData) => {
  try {
    const response = await fetch("https://api.example.com/users", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify(userData),
    });
    if (!response.ok) {
      throw new Error("Failed to create user");
    }
    const data = await response.json();
    console.log("User created:", data);
  } catch (error) {
    console.error("Error:", error);
  }
};
  • looks kind of intense but if you break it down, the only thing that really changed are the options
    • the method is obviously the type of the HTTP method; also it is conventional to use all caps for that
    • the headers section is where you define the type of data you are sending, also notice you send this as an object ({}). "Content-Type": "application/json" is pretty much boilerplate for this type of stuff. if you want to send a file or an image or something you would use Content-Type": multipart/form-data
    • headers is important because this is also where you will put authorization information. most APIs are not available to the public so you usually need to provide an API key to access it, usually even with GET requests. Bearer tokens are pretty common, you would include one like this:
const createUser = async (userData) => {
  try {
    const response = await fetch("https://api.example.com/users", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        Authorization: `Bearer ${anAPIkey}`,
      },
      body: JSON.stringify(userData),
    });
    if (!response.ok) {
      throw new Error("Failed to create user");
    }
    const data = await response.json();
    console.log("User created:", data);
  } catch (error) {
    console.error("Error:", error);
  }
};
// make sure "Authorization" is in quotes to be a string, my editor keeps removing them for some reason 🫠
  • in the body we send the userData (or whatever you are sending to the API). JSON.stringify() (opens in a new tab) is standard for sending JSON data to an API endpoint. the body of a fetch request needs to be a string when sending JSON data, JSON.stringify() is used to serialize the userData object into a JSON-formatted string. this is important because the HTTP protocol can't directly transmit JavaScript objects; it sends strings over the network

  • to perform PUT and DELETE requests, you pretty much only have to change the HTTP method