Web Application Development
INF3048 – S6
HTTP and RESTful Communication between
Web Clients and Servers
Jacques Robin and Camilo Correa © 2023-2024
Outline
The request-response communication pattern between a web
front-end client and a web back-end server
The HyperText Transfer Protocol (HTTP)
The REpresentational State Transfer (REST)
REST(ful) API
The Fetch browser API to request resource from a web client
to a web server
json-server: a simple tool to fake a REST API
postman: a simple tool to fake a REST API client and debug
the REST API server
The Request-Response Pattern
All front-end back-end communications are initiated from the front-end
(a.k.a., pull only)
The front-end sends a request object for a resource available from the
back-end
The server answers to the request by sending a response object which
payload contains the requested resource
The Request-Response Pattern
The requested resource can be anything:
A server-side generated (a.k.a., prerendered) HTML page
A CSS style sheet
A JavaScript module
JSON data
A static media file (image, audio, video)
The code of a 2D or 3D graphic or animation to be executed by the
front-end, etc.
It can be:
Persistently stored in the back-end, or
Dynamically generated by the back-end from other resources accessible
to the server
The Request-Response Pattern
Figure source: https://developer.mozilla.org/en-US/docs/Web/HTTP/Overview
The HyperText Transfer Protocol (HTTP)
An application layer protocol
Basic version is simple
But extensible through headers
Using HTTP, the interaction
between:
The browser (or more generally user
agents) where the frontend of a web
app is running, and
The web server
(software and hardware), where the
backend of the web app is running Figure source:
Can be mediated by multiple intervining https://developer.mozilla.org/en-US/docs/Web/HTTP/Ov
erview
machines and software called web
proxies
That perform functions like:
Content caching to improve content load speed
Load balancing
Authentication
Content filtering (for security, parental control, etc.)
Logging
etc.
HTTP Messages
Reminder: URL structure
Reminder: URL Examples
HTTP Messages
HTTP Methods (a.k.a. verbs)
GET to fetch a resource at a given URL
The response to a GET request contains the resource in its body property
POST to create a new resources at a given URL
The body of a POST request contains the resource to create on the server
at the given URL
The resource has been typically created by the user filling an HTML form
The default MIME type of the body is:
application/x-www-form-urlencoded
It can be more legibly separated by using the alternative
multipart/form-data MIME type
Examples:
https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST#example
The choice between the two can be indicated in:
The enctype attribute of a <form> element
The formenctype attribute of an <input> or <button> element
HTTP Methods
PUT to either:
Update an existing resources with a new value at the given URL, or
Create a new resources if there was non on the server at the given URL
The response to a creation PUT is 201 (Created)
The response to a update PUT is 200 (OK)
Example:
https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/PU
T#example
PUT differs from POST by being idempotent:
After successive PUT requests sent with the same URL and payload the
server is in the same state than after a single such request
After successive POST requests sent with the same URL and payload,
creates multiple copies of the resource on the server
HTTP Methods
DELETE to delete a resource from a given URL
HEAD to prefetch only the headers that would come with a
GET request at a given URL
Useful for GET request conditional upon the header content
GET, DELETE and HEAD are idempotent
REpresentational Stateless Transfer (REST)
Scientifically:
Architecture pattern from Roy Fielding’s 2000 PhD thesis
Independent of the web
Specialization of client-server pattern
Maximize decoupling between client and server to allow a wide
heterogeneity of different clients to use the same service
Request-Response client-server interaction
Request content by name independently of type and underlying
implementation
Response is a textual representation of the resource
Stateless: client state not stored on server but sent by client in each
request needing it to be properly processed by the server
REpresentational Stateless Transfer (REST)
Vernacularly for web development:
Communication protocol is HTTP
Requested content name is a URL
Textual response representation uses a mark-up language
HTML, JSON, YAML, XML, etc.
HTTP verbs are loosely matched to CRUD DB operations for requests that
are direct DB access with no mediating calls to complex business logic
functions:
POST → Create resource (not idempotent)
GET → Read resource
PUT→ Update resource
DELETE → Delete resource
Upsert DB operation and resource nesting (e.g., collections of items structured as
attribute sets) make the difference between Create and Update not very crisp
Hence, usage of POST vs. PUT in RESTful API is rarely strictly followed
Resource structural nesting can be reflected by URL route nesting
With PUT item id must be sent by the client
While with POST it can be created by the server and returned in the response
Session information in URL and cookies are not strictly RESTful
HTTP Status Codes
https://www.restapitutorial.com/httpstatuscodes.html
The Fetch API of web browsers
Built-in API of web browsers to communicate with a remote
web service over the network using HTTP requests and
responses
Asynchronous since based on JavaScript promises
The fetch() window method takes as argument either:
Just the URL of a web resource in case of a simple GET request
Since GET is the default HTTP method when none is specified
or both URL and an HTTP request object, which can be constructed
using new and specifies the HTTP method and many other optional
properties
or just a request object which must then include the resource URL as a
property
Since a URL can be a local file path, fetch can be used to retrieve and
update data locally
In such case, the promise returned always resolves with a response since
no network is involved and hence no network error can occur
The Fetch API of web browsers
A call to fetch() returns a promise which:
Resolves into another promise which resolves into an HTTP Response
object which includes the resource fetched by the method at that URL
The json() method of the response object returns the response body in
textual form
Rejects into a network error if one occurs
To be safe, fetch calls should thus appear into:
A try-catch structure with async-await
or a raw promise then-catch structure without async-await
fetching a resource at a non-existing URL does not rejects the returned
promise, but resolves it with a response with a status property such as
404 Not Found
Example of simplest fetch(url) call
const getTodos = async () => {
const response = await fetch('todos/luigi.json')
const data = await response.json()
return data
}
getTodos()
.then(data => console.log('resolved:', data))
.catch(err => console.log('rejected:', err.message))
Source: the Net Ninja YouTube channel: Asychronous JS #11
Example of fetch(url, request) call
async function putData(url = "", data = {}) {
// Default options are marked with *
const response = await fetch(url, {
method: "PUT", // *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
body: JSON.stringify(data), // body data type must match "Content-Type" header
});
return response.json(); // parses JSON response into native JavaScript objects
}
putData("https://example.com/answer", { answer: 42 }).then((data) => {
console.log(data); // JSON data parsed by `data.json()` call
JSON Server
NPM package that makes data formatted as JSON files
locally stored on one's computer folder
to be accessible through REST requests
from the front-end side of a web application
It wraps the JSON files containing the data with a local
development web server and a REST API endpoint
allowing testing the REST requests made from the front-end
of a web application
before or independently from the back-end development
JSON Server: installation and
data structure convention
To install it: npm install -g json-server
To simulate a database, structure your JSON files with:
Each top-level key corresponding to a table name in a relational
database or a collection name in a document database
Each top-level value being an array of nested JSON objects, each one
representing a record (a.k.a, row) in a relational database table or a
document in a document database collection
Each such nested JSON object must have a special "id" property that
uniquely identifies it
JSON server: example JSON file following
conventional structure
{
"posts": [
{
"id": 1,
"likes": 30,
"title": "Welcome to the new blog",
"body": "Hello world"
},
{
"id": 2,
"likes": 15,
"title": "How to be a Net Ninja",
"body": "Hi word!"
},
{
"title": "New Quasabase course coming soon!",
"body": "Hello Quasabase!",
"likes": 20,
"id": 3
},
{
"title": "Mario Kart Live review",
"body": "Hello Mario!",
"likes": 69,
"id": 4
}
],
"polls": [
{
"id": 1,
"question": "Do you prefer Vue or React?",
"answerA": "Vue",
"answerB": "React"
}
]
}
JSON server: listening to HTTP requests
In root project folder, running:
json-server --watch <JSON File Path>
makes json-server responds to requests at URL:
http://localhost:3000/<Top Level JSON File Key>
To fetch a specific collection in the file just do:
fetch(http://localhost:3000/<Top Level JSON File Key> + id)
For a POST request of a JSON object without an id property,
json-server adds a new id property automatically to the record
being posted, as a remote web server would do
JSON Server: sorting GET request example
const container = document.querySelector('.blogs')
const renderPosts = async (term) => {
let uri = 'http://localhost:3000/posts?_sort=likes&_order=desc'
const res = await fetch(uri)
const posts = await res.json()
let template = ''
posts.forEach(post => {
template += `
<div class="post">
<h2>${post.title}</h2>
<p><small>${post.likes} likes</small></p>
<p>${post.body.slice(0, 200)}...</p>
<a href="/details.html?id=${post.id}">Read more</a>
</div>
`
})
window.addEventListener('DOMContentLoaded', () => renderPosts());
Source: the Net Ninja YouTube channel: Up & Running with JSON Server
JSON Server: text search request example
const container = document.querySelector('.blogs')
const searchForm = document.querySelector('.search')
const renderPosts = async (term) => {
let uri = 'http://localhost:3000/posts?_sort=likes&_order=desc'
if (term) {uri += `&q=${term}`}
const res = await fetch(uri)
const posts = await res.json()
let template = ''
posts.forEach(post => {
template += `
<div class="post">
<h2>${post.title}</h2>
<p><small>${post.likes} likes</small></p>
<p>${post.body.slice(0, 200)}...</p>
<a href="/details.html?id=${post.id}">Read more</a>
</div>
`
})
window.addEventListener('DOMContentLoaded', () => renderPosts());
Source: the Net Ninja YouTube channel: Up & Running with JSON Server
JSON Server: POST request example
const form = document.querySelector('form')
const createPost = async (e) => {
e.preventDefault()
const doc = {
title: form.title.value,
body: form.body.value,
likes: 0,
}
await fetch('http://localhost:3000/posts', {
method: 'POST',
body: JSON.stringify(doc),
headers: { 'Content-Type': 'application/json' }
})
window.location.replace('/')
}
Source: the Net Ninja YouTube
form.addEventListener('submit', channel: Up & Running with JSON Server
createPost);
JSON Server: DELETE request example
const id = new URLSearchParams(window.location.search).get('id')
const container = document.querySelector('.details')
const deleteBtn = document.querySelector('.delete')
const renderDetails = async () => {
const res = await fetch('http://localhost:3000/posts/' + id)
if (!res.ok) {window.location.replace("/")}
const post = await res.json()
const template = `
<h1>${post.title}</h1>
<p>${post.body}</p>
`
container.innerHTML = template;
}
deleteBtn.addEventListener('click', async () => {
const res = await fetch('http://localhost:3000/posts/' + id, {
method: 'DELETE'
});
window.location.replace("/");
})
Source: the Net Ninja YouTube channel: Up & Running with JSON Server
window.addEventListener('DOMContentLoaded', renderDetails);
HTTP Cross-Origin Resource Sharing (CORS)
Source: https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
CORS
Origin of a web resource: the triple (scheme, domain, port) of its URL
By default, web browsers enforce the same-origin security policy
restricting JavaScript code loaded from one origin to make HTTP
request to another origin, to prevent cross-site scripting attacks
e.g., the JavaScript inside the HTML page initially requested to server
with origin O1, contains a call to Fetch making a request to another
server of origin O2 to get some JSON data
e.g., access public API from dev server
HTTP CORS headers are meant to allow cross-origin requests
overriding the same-origin default
They require value modifying requests such as PUT and DELETE
coming from another origin to be preceded by so-called preflight
OPTION request
The CORS headers in the response to the OPTION request will tell
the client what URL is allowed to modify the resource with what
method
CORS
Example: clone the jsonServerWebStoragePersistent branch of
GitLab calcquasarstudents repository,
https://gitlab.esiea.fr/webdev/calcquasarstudents/-/tree/JsonS
erverWebStoragePersistent?ref_type=heads
Launch the calculator app using "quasar dev" or "npm run
dev" on one terminal
On another terminal launch json-server using
"npm run json-serve"
In the browser running the calculator app, open More Tools /
Developer Tools and then the Network tab and in it the All
subtab
Then click on a GET button of the app: only one get request
appears
CORS
Check the Headers tab, then the
properties:
General /
Referrer Policy property
Response Headers /
Access-Contol-Allow-Methods
Response Headers /
Access-Contol-Allow-Origin
Request Headers /
Host
Request Headers /
Origin
Request Headers /
Referer
and the Response tab
CORS
The Response Headers:
Access-Control-Allow-Methods: GET, HEAD, PUT, PATCH, POST, DELETE
Access-Control-Allow-Origins: *
are set in such permissive way by json-server, because it is a tool to fake
a real API for HTTP Request testing during development
For security reasons, these values should not be used in
production
Now click on a PUT button on the app
This time 2 new requests appear on the All subtab
One OPTIONS request with no Payload and an empty
Response containing only the specification of the CORS policy
in the Response Headers
One PUT request containing the Payload with the new value
to update
CORS
OPTIONS Request:
Headers:
Response:
Note that the preflight
OPTIONS Request was sent
automatically by the browser,
not programmed manually
using Fetch
CORS
PUT Request:
Headers:
Response:
More on HTTP
HTTP cookie:
https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies
HTTP Cross-Origin Resource Sharing (CORS):
https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
HTTP authentication:
https://developer.mozilla.org/en-US/docs/Web/HTTP/Authenti
cation
HTTP authorization headers:
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers
/Authorization
HTTP caching:
https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching
HTTP security policy headers:
Postman
A tool to test a REST API without a front-end app
Converse of json-server
Together they allow defining and testing a REST interface
through which the front-end and back-end shall interact,
independently of the implementation of both ends
Provides a GUI to specify HTTP requests to an URL and get
their responses
Download here: https://www.postman.com/downloads/
Postman