visit
I covered terms and definitions necessary for understanding HTTP messages in part 1. Then, I demonstrated how to generate response messages in part 2. So we know before sending the request message with instructions for the server, the client has to encode it and attach the information the server will need to decode it. Then, after the client receives a response back from the server, it will also need to be decoded. Let's dive in to the code required to do all that.
This article assumes familiarity with basic JavaScript, , and the terms defined in part 1. It also assumes you followed part 2.
Now, we need a client! At the same level as your server folder, make a folder called client. Navigate to the folder in a terminal and run npm install react
.
After it finishes installing, run npx create-react-app <app-name>
and follow the prompts. When you see <word>
in code, it means replace <word>
with your own new word without the < >
. So for my code, I ran npx create-react-app http101
. is bundled with npm. Where npm installs packages, npx executes/runs them. will generate all the files you need for a React app.
After it's done, in addition to the new folders and files, you should see "Success! Created <app-name>
" and other successful build logs in your terminal. Run npm start
, and you should see build logs and "Compiled successfully!". Your browser should open a new tab navigated to the URL //localhost:3000/
with the boilerplate React app displayed.
Open client > src > index.js
in your text editor. This file is the JavaScript entry point. In other words, any components we create need to be imported here to be rendered when the app runs. I import my 3 components like this:
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import XHR from './Components/XHR';
import Fetch from './Components/Fetch';
import Axios from './Components/Axios';
import reportWebVitals from './reportWebVitals';
ReactDOM.render(
<React.StrictMode>
<XHR />
<Fetch />
<Axios />
</React.StrictMode>,
document.getElementById('root')
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: //bit.ly/CRA-vitals
reportWebVitals();
import React from 'react';
function Fetch() {
return (
<p>"This is how I make Fetch a thing."</p>
)
}
export default Fetch;
The other two look almost exactly the same. When I run npm start
from my terminal and my browser tab opens //localhost:3000
, I see all three of my components' strings displayed.
Before we get into the code for the HTTP requests individually, let's talk about JavaScript's built-in methods for converting JSON to JavaScript and vice versa. JSON.parse()
takes a JSON object or array and turns it into valid JavaScript. JSON.stringify()
takes valid JavaScript and turns it into valid JSON, sometimes referred to as a JSON string.
let yarn = JSON.parse({
"yarn": {
"id": 5,
"name": "Wonderland Yarns & Frabjous Fibers Mary Ann",
"weight": "Light Fingering",
"meters": 539.5
}
})
console.log(yarn)
let newBody = JSON.stringify(yarn)
console.log(newBody)
It's manually doing what the express.json()
body parser was doing for us in the Express server.
To build a GET request to my /yarn
endpoint, I instantiate an XHR object I've called gXHR
, open the request while passing the method and URL, and then send the request. Finally, I write a gXHR.onload()
function to handle what happens when the response is received. In my gXHR.onload()
function, I parse the JSON I received into a valid JavaScript object with JSON.parse()
and log the first item in the array that was returned.
function getRequest() {
// create a request object
let gXHR = new XMLHttpRequest()
// set method and URL
gXHR.open("GET", "//localhost:8080/yarn")
// send GET request
gXHR.send()
// what happens when the response is received
gXHR.onload = function() {
if (gXHR.status !== 200) {
console.log(gXHR.status, gXHR.statusText)
} else {
let yarns = JSON.parse(gXHR.response)
console.log(yarns[0])
}
}
}
.onload()
is a listener, essentially a loop that runs until the .send()
method finishes. The function I wrote and assigned to .onload()
is a callback function to run after the HTTP request has concluded.
The POST request looks similar, but we also have to pass a body. I start by defining my POST body and passing it to JSON.stringify()
to turn the JavaScript object into JSON. I also have to set the Content-Type
header so XHR knows to send the body formatted as JSON. Then, when I create my request, I pass my JSON string to the pHXR.send()
method. Finally, I don't JSON.parse()
the response in pXHR.onload()
because the response is a string.
function postRequest() {
let body = JSON.stringify({
yarn: {
id: 5,
name: "Wonderland Yarns & Frabjous Fibers Mary Ann",
weight: "Light Fingering",
meters: 539.5
}
})
let pXHR = new XMLHttpRequest()
pXHR.open("POST", "//localhost:8080/yarn/create")
// set request header
pXHR.setRequestHeader('Content-type', 'application/json; charset=utf-8')
pXHR.send(body)
pXHR.onload = function() {
if (pXHR.status !== 200) {
console.log(pXHR.status, pXHR.statusText)
} else {
console.log(pXHR.response)
}
}
}
function deleteRequest() {
let dXHR = new XMLHttpRequest()
dXHR.open("DELETE", '//localhost:8080/yarn/delete/3')
dXHR.send()
dXHR.onload = function() {
if (dXHR.status !== 200) {
console.log(dXHR.status, dXHR.statusText)
} else {
console.log(dXHR.response)
}
}
}
If this was a real app, I would pass the id of the yarn I wanted deleted to deleteRequest()
and add it dynamically to the URL in a template string like this:
`//localhost:8080/yarn/delete/${id}`
However, passing an id to an onClick
handler in React is a tutorial for another time.
I use these functions as onClick
handlers for three buttons:
return (
<section>
<button onClick={getRequest}>GET</button>
<button onClick={postRequest}>POST</button>
<button onClick={deleteRequest}>DELETE</button>
</section>
)
To test, I run the client in one terminal using npm start
and the server in a second terminal using node app.js
. In the browser, I watch the console tab in the . As I press the buttons, the different strings I expect to see appear.
Having worked mainly in newer HTTP packages at this point, XHR feels very manual to me. To write one request, we have to use multiple methods. With Fetch, we can write a GET request in one line. Fetch is also a Web API, so we don't have to import it either. We don't even have to instantiate it - fetch()
is a function all on its own.
function getRequest() {
fetch("//localhost:8080/yarn")
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.log(error))
}
fetch()
is doing a lot of work for us. Because we didn't pass configuration options, Fetch is using the default settings. The method is automatically set to GET. In the first .then()
, response.json()
is using Fetch's built-in JSON parser to convert the JSON body to JavaScript.
fetch("//localhost:8080/yarn").then(response => response.json()).then(data => console.log(data[0])).catch(error => console.log(error))
Because Fetch is based on promises under the hood, it returns a promise object. This means we can use chained .then()
and .catch()
functions to handle the result of the HTTP request. So this code is saying after the promise object is returned as fulfilled, take the response message body and parse the JSON. Then take the newly parsed data and log the first result in the array. If the promise object is instead returned as failed, catch it, and log the error.
Learning the arrow function syntax and async chaining at the same time can be confusing - I know from experience. The .then()
function is waiting for the execution of the function before it to finish and then executing the function passed to it. We could rewrite it like this:
function parseData(response) {
response.json()
}
fetch("//localhost:8080/yarn").then(parseData(response))
function parseData(response) {
return response.json()
}
async function getFetch() {
fetch("//localhost:8080/yarn")
}
function getRequest(callback) {
let response = await getFetch()
callback(response)
}
getRequest(parseData)
Notice I didn't write any error handling to replace .catch()
. .catch()
is there to handle errors that happen within the Fetch request. It is triggered by a JavaScript error. To handle a response with a 500 status code, or server error, I would have to check the response after I've received it to verify it's an error. We'll go into this more in part 4.
To make the POST request to my /yarn/create
endpoint work, we'll have to pass configuration options to the fetch()
method. I start by building my body and converting it to JSON. Then, when I create my Fetch request, I pass a config object after my URL. Finally, because the response is a string, we have to parse it using .text()
instead of .json()
before we can log it to the console.
function postRequest() {
let bodyString = JSON.stringify({
yarn: {
id: 5,
name: "Wonderland Yarns & Frabjous Fibers Mary Ann",
weight: "Light Fingering",
meters: 539.5
}
})
fetch("//localhost:8080/yarn/create", {
method: "POST",
headers: {
'Content-Type': 'application/json'
},
body: bodyString
}).then(response => response.text())
.then(data => console.log(data))
.catch(error => console.log(error))
}
The DELETE method also requires a config object and the .text()
method to parse the body.
function deleteRequest() {
fetch("//localhost:8080/yarn/delete/2", {
method: "DELETE"
})
.then(response => response.text())
.then(data => console.log(data))
.catch(error => console.log(error))
}
Axios is an npm package, so I run npm install axios
in my terminal to install it. I also have to import it at the top of my file:
import axios from 'axios';
Now that it's been imported, we can use the .get()
method to write a request. Since Axios also uses promises, we can still chain with .then()
and .catch()
.
function getRequest() {
axios.get('//localhost:8080/yarn')
.then(response => console.log(response.data[0]))
.catch(error => console.log(error))
}
Right off the bat you can see how this format is closer to the Express server than XHR. Furthermore, you may have noticed I didn't have to parse the body of the response - Axios does that for us. As part of that formatting, the structure of the response object we can reference is changed - instead of response.body
, I have to reference response.data
. Because they have it , I knew to expect that. We'll delve into evaluating response format when you don't know what to expect in part 3.
Next up, the POST request. Thanks to Axios, I don't have to JSON.stringify()
my body and the .post()
method allows you to pass an object to add to the body after the URL.
function postRequest() {
axios.post('//localhost:8080/yarn/create', {
yarn: {
id: 5,
name: "Wonderland Yarns & Frabjous Fibers Mary Ann",
weight: "Light Fingering",
meters: 539.5
}
}).then(response => console.log(response.data))
.catch(error => console.log(error))
}
I didn't even have to pass a header - Axios tries to JSON.stringify()
all request bodies and JSON.parse()
all response bodies. We'll cover how to use the config object to set headers (and method and authentication) in part 4.
Finally, the DELETE request to my /yarn/delete/:id
endpoint, looking much like the GET request:
function deleteRequest() {
axios.delete('//localhost:8080/yarn/delete/1')
.then(response => console.log(response.data))
.catch(error => console.log(error))
}
Also Published