Chapter 3: HTTP, request, response. APP project. Fetch method. API method at Index page. Next-Express server. Express route. Asynchronous function, Promise, async/await. API server. New project API. Updating APP.
We keep our book up to date with recent libraries and packages.
The section below is a preview of SaaS Boilerplate Book. To read the full text, you will need to purchase the book.
In Chapter 3, you will start with the codebase in the 3-begin folder of our saas repo and end up with the codebase in the 3-end folder.
We will cover the following topics in this chapter:
HTTP, request, response
APP project - Step 1: Fetch method - Step 2: API method at Index page - Step 3: Next-Express server. Express route.
Asynchronous execution, callback, Promise, async/await
API server - New project API - Updating APP
In Chapter 2, we successfully integrated our Next.js web application with Material-UI and made many layout-related improvements. We discussed in detail two types of rendering in a Next.js web application: server-side and client-side. We set and ran many tests to understand differences between server-side and client-side rendered pages. However, in those tests, our pages did not contain data fetched from the server. We had static HTML code, but we never had an API method that sent a request to the server to CRUD (create, read, update, delete) data.
The simplest example of data retrieval can be described like this: An end user loads a page of our web application. That page contains some static HTML and CSS code that shows layout and styles. On this page, our goal is to show the user some data in addition to the static code. The data could be the user's picture or email, so the user knows that he/she is properly authenticated on this page. To implement such a scenario, we have to: - Create an API method that sends a request from the user's browser to our server - In return, our server sends a request to our database - Once our server receives data from our database, it sends a response with the attached data to the user's browser
The above is just a simple example of data retrieval that may happen for a client-side rendered page. Once you finish this book, your final web application will have dozens of API methods to not only read data but also do other data transformations.
In this chapter, we will only focus on reading the user's email from our application's server. The main focus is on two-project architecture. Normally, with one-project architecture, we have browser-side and server-side code inside the same project. With two-project architecture (app
and api
), we will have app
that primarily has pages' code. This code can run on both browser and server, depending on the type of rendering (client-side or server-side rendering). app
is either on the browser or the server, and app
calls API methods that send requests to the api
server. Code at the api
project is server-only. The api
server sends a request to MongoDB's server to CRUD data from our database, then sends a response to app
, which is either on browser or server depending on the type of rendering.
Here is a typical internal API infrastructure and request-response cycles in our SaaS boilerplate:
We will discuss later the difference between internal and external API infrastructure (or API for short).
In the section below, we will learn about the concepts of HTTP request and HTTP response.
HTTP, request, response link
Before we can discuss fetching data in our web application, we need to get familiar with the basic concepts of HTTP, request, and response.
Simply put - HTTP (HyperText Transfer Protocol) is a set of rules (protocol
) that governs data exchange on the web. These rules specify how a client
(typically a web browser, called client because it is served data by a server) and server
(typically a web server, a machine that sends data to a client) exchange messages. These messages are request
(sent by the client to the server) and response
(sent by the server to the client in response to a request). The data can be an HTML document, image, JSON or practically any other type of data.
HTTP is currently the most popular protocol on the web. You've probably noticed prepended http
or https
on web addresses:
https://developer.mozilla.org/en-US/docs/Web/HTTP/Overview
Why is it important for us to understand HTTP? Because when building any data exchange for our web application, we will be thinking about it in terms of a request
sent from the browser to the server and a response
sent from the server to the browser. We will use so-called API methods to send requests from the browser to the server, and we will use so-called Express routes on the server to get data and respond back to the browser.
In this chapter, our goal is to set up infrastructure to display a user's email address on the Index
page. We want to write code in the following way: When a user loads the Index
page, the user's browser (the client
) sends a request
to the server
. The server will process the request
and send back a response
that will have a body
parameter that contains an email address.
Both request
and response
are HTTP messages:
https://developer.mozilla.org/en-US/docs/Web/HTTP/Messages
request
is an HTTP message that is sent from the browser to the server:
https://developer.mozilla.org/en-US/docs/Web/HTTP/Messages#http_requests
response
is also an HTTP message, but it is sent from the server to the browser (the client):
https://developer.mozilla.org/en-US/docs/Web/HTTP/Messages#http_responses
The request
has multiple properties. In JavaScript, an object that corresponds to a request
can contain these properties among other: version of protocol
, path
, method
, credentials
, and headers
(Content-Type
and Cookie
headers).
https://developer.mozilla.org/en-US/docs/Web/API/Request
The properties we will specify are listed below.
version of protocol
is the version of the HTTP protocol - either HTTP/1.1 or HTTP/2.0. The latter is designed to have lower latency for the end user. Read more about it here.
url
or path
property. path
is a relative route of the resource. Relative means that it does not include the protocol name (https://), main domain (say, builderbook.org), or port (443). In the example above, the path
is /_next/cb2af84e5f28446e3dd58f1d53357d95/server.js
:
https://developer.mozilla.org/en-US/docs/Web/API/Request/url
method
property. An HTTP method
is an operation that the browser wants to perform. Most often, the browser gets data (say, a list of books) with the GET
method or posts data (e.g creates a new book using the form's data) with the POST
method. Other methods are available for more rare operations:
https://developer.mozilla.org/en-US/docs/Web/API/Request/method
credentials
property controls whether the client (the browser) attaches cookie
(Cookie
header with value) to request
. The server can use cookie
to identify a unique session
and user. For example, cookie
can be used to create acpersistent session for a logged-in user. Check up possible values for the credentials
property:
https://developer.mozilla.org/en-US/docs/Web/API/Request/credentials
headers
provide more descriptions to the server. Among the many properties on the screenshot above, you'll notice dnt: 1
. This parameter tells the server do not track
.
On Do Not Track
header:
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/DNT
On Content-Type
header:
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type
On Cookie
header:
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cookie
request
has an optional body
property (not all requests and responses have one):
https://developer.mozilla.org/en-US/docs/Web/HTTP/Messages#body
req.body
contains data and is typically used by a request
with the method POST
to send data from the client to the server. For example, when an end user creates a new Post or Discussion in our web application. For a request
with the method GET
, we don't specify body
to send data. Typically, we will use req.query
to send string-type data - for example, slug.
A response
contains version of protocol
, status code
, status message
, and optional headers
or body
.
https://developer.mozilla.org/en-US/docs/Web/API/Response
We already covered version protocol
, headers
, and body
when discussing request
.
status code
indicates whether a request succeeded or failed. status message
is typically a one-word description that accompanies a status code
.
Take a look at our screenshot of a typical response. The response status 200 OK
says that our request succeeded. Success means that the response's body contains the data that we requested with the GET method
and path
.
If our request
used the POST method instead, then 200 OK
would mean that data inside the request
's body was successfuly sent and received by the server.
A full list of status codes is here:
https://developer.mozilla.org/en-US/docs/Web/HTTP/Status
In Chapter 4, when we add cors
configuration to our server (api
project), our server will add Access-Control-Allow-Origin
header to the server's response. This response
header controls whether requesting code can access a response from the server. Since our app
and api
projects will be served at different domains, setting up cors
configuration that allows app
access to a response from api
is a must:
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin
APP project link
So now that you know about HTTP request and HTTP response, we can write our first API method that sends a request and receives a response from the server.
We can start making changes to the app
project at the book/3-begin
location. Our final goal is to display the email address of a user when the user loads the Index
page.
We can split our entire task into three parts:
Call the
fetch
method with a constructedrequest
object androute
(same website for now, later to a different website) as arguments. Receive aresponse
object and return data (which is the JSON-parsedbody
of theresponse
). DefinesendRequestAndGetResponse
using thefetch
method.Define an API method using the
sendRequestAndGetResponse
method. This API method should be executed when the end user loads theIndex
page. The API method calls thesendRequestAndGetResponse
method, which in turn, calls thefetch
method. The final result is theIndex
page getting data from theresponse
object'sbody
property.On the server, we need some way to detect an incoming
request
at a particularroute
. We should also retrieve requested data and send it back to requesting code as aresponse
. Since we plan to use an Express server, such construct is called an Express route. A handler function of an Express route gets executed once our Express server receives arequest
with a matchingroute
. Typically, the goal of anExpress route
is to retrieve data from a database and send aresponse
with the data attached. Since we are not discussing database in this chapter, we will simply hardcode the requested data inside the Express route.
Step 1: Fetch method link
Our app
web application, on both the server and the client, is a JavaScript application. In JavaScript, you can use the fetch
method to send a request
to the server and to receive a response
. The method is called fetch
:
https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch
From the above documentation:
// Example POST method implementation:
async function postData(url = '', data = {}) {
// Default options are marked with *
const response = await fetch(url, {
method: 'POST', // *GET, POST, PUT, DELETE, etc.
mode: 'cors', // no-cors, *cors, same-origin
cache: 'no-cache', // *default, no-cache, reload, force-cache, only-if-cached
credentials: 'same-origin', // include, *same-origin, omit
headers: {
'Content-Type': 'application/json'
// 'Content-Type': 'application/x-www-form-urlencoded',
},
redirect: 'follow', // manual, *follow, error
referrerPolicy: 'no-referrer', // no-referrer, *client
body: JSON.stringify(data) // body data type must match "Content-Type" header
});
return await response.json(); // parses JSON response into native JavaScript objects
}
postData('https://example.com/answer', { answer: 42 })
.then((data) => {
console.log(data); // JSON data parsed by `response.json()` call
});
What do we see here? - fetch
takes the url
string and request
object as arguments - fetch
returns a response
object - the function postData
has async
in front of it, and fetch
has await
in front of it
Both the postData
and fetch
method are asynchronous - that means each of these functions returns a Promise
.
In JavaScript, code executes from top to bottom. But if there is some kind of error in the middle of the code, execution will stop at that point. To avoid this, JavaScript offers the asynchronous function. A code block with an asynchronous function does not need to complete execution - the rest of the code can still be executed in synchronous fashion.
Why do we need an asynchronous function? Imagine we built a SaaS boilerplate where fetching data was not an asynchronous operation. Every time a user sends a data request to the server, this user and our web application won't be able to do anything else on our web application. The web application will not function while the browser is waiting for data. Imagine multiple users blocking each other.
We dedicated a separate section of this chapter to explain Promise
and its syntactic sugar async/await
in detail. postData
and fetch
are asynchronous. Usage of await
is optional for an asynchronous function. In the above example, we defined the asynchronous postData
function and added await
in front the fetch
method. It's important to remember that await
can be added only in front of another asynchronous function - in our case, this function is fetch
. Adding await
makes JavaScript pause on the line that has await
until, in this case, the asynchronous method fetch
returns data.
Later in this subsection, we will create an asynchronous method with async function sendRequestAndGetResponse
. Inside this method's definition, we will use await fetch
.
Let's follow the above example for the asynchronous postData
function and define our own asynchronous sendRequestAndGetResponse
method. Create a new file, book/3-begin/app/lib/api/sendRequestAndGetResponse.ts
, with the following content:
import 'isomorphic-unfetch';
export default async function sendRequestAndGetResponse(path, opts: any = {}) {
const headers = Object.assign(
{},
opts.headers || {},
{
'Content-type': 'application/json; charset=UTF-8',
},
);
const { request } = opts;
if (request && request.headers && request.headers.cookie) {
headers.cookie = request.headers.cookie;
}
const qs = opts.qs || '';
const response = await fetch(
`${process.env.NEXT_PUBLIC_URL_APP}${path}${qs}`,
Object.assign({ method: 'POST', credentials: 'include' }, opts, { headers }),
);
const text = await response.text();
if (response.status >= 400) {
throw new Error(response.statusText);
}
try {
const data = JSON.parse(text);
return data;
} catch (err) {
if (err instanceof SyntaxError) {
return text;
}
throw err;
}
}
Why do we need to add and import a new dependency, isomorphic-unfetch
?
By default, not all clients (browsers) and servers support the fetch
method. The package isomorphic-unfetch
makes fetch
globally available in our code on both the client and on the server:
https://www.npmjs.com/package/isomorphic-unfetch
This package switches between unfetch
and node-fetch
for the browser and the server, respectively.
Why do we need fetch
on both client and server? Because our Next.js web application can render pages on both the client and the server. In Chapter 2, we discussed in detail when our Next.js web application renders pages on the client and when on the server - it depends on how a user accessed the page. - For a client-side rendered page, fetch
runs on the browser, and the app
code on the browser sends a request to the app
code on the server. - For a server-side rendered page, fetch
runs on the server, and the app
code on the server sends a request to itself.
Thus, we need to make sure that the fetch
method is available on both the client and the server.
Let's look more closely into the above code:
You've reached the end of the Chapter 3 preview. To continue reading, you will need to purchase the book.
We keep our book up to date with recent libraries and packages.