CT313H:
Web Technologies and Services
Bùi Võ Quốc Bảo
(bvqbao@cit.ctu.edu.vn)
Cần Thơ, 2023
Credit
● The slides are inspired by the CS193X course created by
Victoria Kirst
Servers
Server-side programming
“Client-side" programming:
- The code we write gets run in a browser on the user's
(client's) machine
“Server-side” programming:
- The code we write gets run on a server
- Servers are computers run programs to generate web
pages and other web resources
Recall…
When you navigate to a URL:
- Browser creates an HTTP GET request
- Operating system sends the GET request to the server over TCP
When a server computer receives a message:
- The server's operating system sends the message to the server
software (via a socket)
- The server software then parses the message
- The server software creates an HTTP response
- The server OS sends the HTTP response to the client over TCP
Client Server
(Routing,
etc…)
"Server"
The definition of server is overloaded:
- Sometimes "server" means the machine/computer that
runs the server software.
- Sometimes "server" means the software running on the
machine/computer.
You have to use context to know which is being meant
Sockets
Q: What does it mean for a program to be "listening" for
messages?
When the server first runs, it executes code to create a
socket that allows it to receive incoming messages from
the OS
A socket is one end of a communication channel. You can
send and receive data on sockets
However, NodeJS will abstract this away so we don't have
to think about sockets
Servers
Sometimes when you type a URL in your browser,
the URL is a path to a file on the internet:
- Your browser connects to the host address
and requests the given file over HTTP
- The web server software (e.g. Apache) grabs
that file from the server's local file system,
and sends back its contents to you
But that's not always the case
Web Services
Other times when you type a URL into your
browser, the URL represents an API endpoint,
and not a path to a file
That is:
- The web server does not grab a file from
the local file system, and the URL is not
specifying where a file is located
- Rather, the URL represents a
parameterized request, and the web
server dynamically generates a response
to that request
NodeJS
NodeJS
NodeJS:
- A JavaScript runtime written in C++
- Can interpret and execute JavaScript
- Includes support for the NodeJS API
NodeJS API:
- A set of JavaScript libraries that are useful for creating
server programs
V8 (from Chrome):
- The JavaScript interpreter ("engine") that NodeJS uses
to interpret, compile, and execute JavaScript code
NodeJS
NodeJS:
- A JavaScript runtime written in C++
Q: What does
- Can interpret and execute JavaScript this mean?
- Includes support for the NodeJS API
NodeJS API:
- A set of JavaScript libraries that are useful for creating
server programs
V8 (from Chrome):
- The JavaScript interpreter ("engine") that NodeJS uses
to interpret, compile, and execute JavaScript code
First: Chrome
Chrome:
- A browser written in C++
- Can interpret and execute JavaScript code
- Includes support for the DOM APIs
DOM APIs:
- JavaScript libraries to interact with a web page
V8:
- The JavaScript interpreter ("engine") that Chrome uses
to interpret, compile, and execute JavaScript code
Chrome, V8, DOM
Parser
JavaScript DOM API
Execution runtime Implementation
Engine
(Call stack,
Garbage memory, etc.)
Collector
Parser
JavaScript DOM API
Execution runtime Implementation
Engine
(Call stack,
Garbage memory, etc.)
Collector
const name = 'V8';
"Please execute
console.log()"
Parser
JavaScript DOM API
Execution runtime Implementation
Engine
(Call stack,
Garbage memory, etc.)
Collector
console.log('V8');
NodeJS, V8, NodeJS APIs
Parser
JavaScript NodeJS API
Execution runtime Implementation
Engine
(Call stack,
Garbage memory, etc.)
Collector
Parser
JavaScript NodeJS API
Execution runtime Implementation
Engine
(Call stack,
Garbage memory, etc.)
Collector
const x = 15;
x++;
"Please execute
http
.createServer()"
Parser
JavaScript NodeJS API
Execution runtime Implementation
Engine
(Call stack,
Garbage memory, etc.)
Collector
http.createServer();
Parser
JavaScript NodeJS API
Execution runtime Implementation
Engine
(Call stack,
Garbage memory, etc.)
Collector
What if you tried to call
document.querySelector('div');
in the NodeJS runtime?
Parser
JavaScript NodeJS API
Execution runtime Implementation
Engine
(Call stack,
Garbage memory, etc.)
Collector
document.querySelector('div');
ReferenceError: document is not defined
Parser
JavaScript NodeJS API
Execution runtime Implementation
Engine
(Call stack,
Garbage memory, etc.)
Collector
What if you tried to call console.log('nodejs');
in the NodeJS runtime?
"Please execute
console.log()"
Parser
JavaScript NodeJS API
Execution runtime Implementation
Engine
(Call stack,
Garbage memory, etc.)
Collector
console.log('nodejs');
(NodeJS API implemented their own console.log)
NodeJS
NodeJS:
- A JavaScript runtime written in C++
- Can interpret and execute JavaScript
- Includes support for the NodeJS API
NodeJS API:
- A set of JavaScript libraries that are useful for creating
server programs
V8 (from Chrome):
- The JavaScript interpreter ("engine") that NodeJS uses
to interpret, compile, and execute JavaScript code
Installation
NodeJS installation:
- https://nodejs.org/en/download/
- https://github.com/nvm-sh/nvm
- https://github.com/coreybutler/nvm-windows
node command
Running node without a filename runs a read-eval-print
loop (REPL)
- Similar to the JavaScript console in Chrome, or when
you run "python"
$ node
> let x = 5;
undefined
> x++
5
> x
6
NodeJS
NodeJS can be used for writing scripts in JavaScript,
completely unrelated to servers
simple-script.js
function printPoem() {
console.log('Roses are red,');
console.log('Violets are blue,');
console.log('Sugar is sweet,');
console.log('And so are you.');
console.log();
}
printPoem();
printPoem();
node command
The node command can be used to execute a JS file:
$ node fileName
$ node simple-script.js
Roses are red,
Violets are blue,
Sugar is sweet,
And so are you.
Roses are red,
Violets are blue,
Sugar is sweet,
And so are you.
Node for servers
Here is a very basic server written for NodeJS:
(WARNING: We will not actually be writing servers like this!!!
We will be using ExpressJS to help, but we haven't gotten there yet
require()
const http = require('http');
const server = http.createServer();
The NodeJS require() statement loads a module, similar
to import in Java or include in C/C++
- We can require() modules included with NodeJS, or
modules we've written ourselves
- In this example, 'http' is referring to the HTTP
NodeJS module
require()
const http = require('http');
const server = http.createServer();
The http variable returned by require('http') can be
used to make calls to the HTTP API:
- http.createServer() creates a Server object
EventEmitter.on
The on() function is the NodeJS equivalent of
addEventListener
EventEmitter.on
The request event is emitted each time there is a new
HTTP request for the NodeJS program to process
Server
EventEmitter.on
The req parameter gives information about the incoming
request, and the res parameter is the response parameter
that we write to via method calls
- statusCode: Sets the HTTP status code
- setHeader(): Sets the HTTP headers
- end(): Writes the message to the response body then
signals to the server that the message is complete
listen() and listening
The listen() function will make the program start
accepting messages sent to the given port number
- The listening event will be emitted when the server
has been bound to a port
Running the server
When we run node server.js in the terminal, we see
the following:
The process does not end after we run the command, as it is
now waiting for HTTP requests on port 3000
Server response
Here is the result of the request to our HTTP server:
Node for servers
This server
returns the same
response no
matter what the
request is
Node for servers
The NodeJS server APIs
are actually pretty low-
level:
- You build the
request manually
- You write the
response manually
- There's a lot of
tedious processing
code
Express
We're going to use a library called Express on top of
NodeJS:
Express routing
Express
However, Express is not part of the NodeJS APIs
If we try to use it like this, we'll get an error:
We need to install Express via npm
npm
When you install NodeJS, you also install npm:
- npm: Node Package Manager*:
Command-line tool that lets you install packages
(libraries and tools) written in JavaScript and
compatible with NodeJS
- Can find packages through the online repository:
https://www.npmjs.com/
*though the creators of "npm" say it's not an
acronym (as a joke -_-)
npm install and uninstall
npm install package-name
- This downloads the package-name library into a
node_modules folder
- Now the package-name library can be included in your
NodeJS JavaScript files
npm uninstall package-name
- This removes the package-name library from the
node_modules folder, deleting the folder if necessary
Express example
$ npm install express
$ node server.js
Example app listening on port 3000!
Express routes
You can specify routes in Express:
Express routes
app.method(path, handler)
- Specifies how the server should handle HTTP method
requests made to URL/path
- This example is saying:
• When there's a GET request to
http://localhost:3000/hello, respond with the text
"GET hello!"
Handler parameters
Express has its own Request and Response objects:
- req is a Request object
- res is a Response object
- res.send() sends an HTTP response with the given
content
• Sends content type "text/html" by default
Querying our server
HTTP requests
Our server is written to respond to HTTP requests (GitHub):
Q: How do we sent HTTP requests to our server?
Querying our server
Here are four ways to send HTTP requests to our server:
1. Navigate to http://localhost:3000/<path> in our browser
− Caveat: Can only do GET requests
2. Postman Web/HTTP API client
3. curl command-line tool
4. Call fetch() in web page
Postman
https://www.postman.com/
curl
curl: Command-line tool to send and receive data from a
server (Manual)
curl –d '…' –H '…' -X METHOD url
e.g.
$ curl -X GET http://localhost:3000/
Querying with fetch()
JavaScript client
code in a web page:
fetch() to localhost
But if we try fetching to localhost from file://
We get this CORS error:
CORS
CORS: Cross-Origin Resource Sharing (wiki)
- Browser policy for what resources a web page can load
- An origin = protocol + host + port
- You cannot make cross-origin requests by default for:
• Resources loaded via fetch() or XHR
The problem is that we are trying to fetch()
http://localhost:3000 from file:///
- Since the two resources have different origins, this is
disallowed by default CORS policy
CORS
https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
Cross-origin solutions
The problem is that we are trying to fetch()
http://localhost:3000 from file:///
Two ways to solve this:
1. Change the server running on localhost:3000 to
allow cross-origin requests, i.e. to allow requests
from different origins (such as file:///)
2. Preferred solution: Load the frontend code statically
from the same server, so that the request is from the
same origin
Solution 1: Enable CORS
You can set an Access-Control-Allow-Origin HTTP header
before sending your response
- This is the server saying to the browser in its response:
"Hey browser, I'm totally fine with websites of any origin
requesting this file"
Solution 1: Enable CORS
Now the fetch will succeed:
Cross-origin solutions
However, you wouldn't have to enable CORS at all if you
were making requests from the same origin
Preferred solution: Load the frontend code statically
from the same server, so that the request is from the
same origin
Recall: Web services
Sometimes when you type a URL into your
browser, the URL represents an API endpoint
That is, the URL represents a parameterized
request, and the web server dynamically
generates a response to that request
That's how our NodeJS server treats routes
defined like this:
Recall: Servers
Other times when you type a URL in your
browser, the URL is a path to a file on the hard
drive of the server:
- The web server software grabs that file from
the server's local file system, and sends back
its contents to you
We can make our NodeJS server also sometimes serve files
"statically," meaning instead of treating all URLs as API
endpoints, some URLs will be treated as file paths
Solution 2: Statically served files
This line of code makes our server now start serving the
files in the 'public' directory directly
Server static data
Now Express will serve:
http://localhost:3000/fetch.html
http://localhost:3000/fetch.js
Express looks up the files relative to the static directory, so
the name of the static directory ("public" in this case) is not
part of the URL
Sending data to the server
Route parameters
A parameter defined in the URL of the request is often called a
"route parameter"
Example:
https://jsonplaceholder.typicode.com/users/10
The last part of the URL is a parameter representing the user
id, which is 10
Route parameters
Q: How do we read route parameters in our server?
A: We can use the :variableName syntax in the path to
specify a route parameter (Express docs):
We can access the route parameters via req.params
Route parameters
Route parameters
You can define multiple route parameters in a URL (docs):
Query parameters
Example:
https://jsonplaceholder.typicode.com/posts?userId=1
The query parameter sent to the server endpoint is userId,
whose value is 1
Query parameters
Q: How do we read query parameters in our server?
A: We can access query parameters via req.query:
POST message body
POST message body
Content-Type: application/json
{"name": "Bao Bui", "email": "bao@example.com"}
Content-Type: application/x-www-form-urlencoded
name=Bao%20Bui&email=bao%40example.com
POST message body
POST message body
$ curl -d '{"name":"Bao Bui", "email":"bao@example.com"}'
-H 'Content-Type: application/json' -X POST
http://localhost:3000/hello
POST message body
$ curl --data-urlencode 'name=Bao Bui' --data-urlencode
'email=bao@example.com' -H 'Content-Type:
application/x-www-form-urlencoded' -X POST
http://localhost:3000/hello
POST message body
HTTP Response Message
Recap
You can deliver parameterized information to the server in
the following ways:
1. Route parameters
2. GET request with query parameters
(DISCOURAGED: POST with query parameters)
3. POST request with message body
Q: When do you use route parameters vs query
parameters vs message body?
GET vs POST
● Use GET requests for retrieving data, not writing data
● Use POST requests for writing data, not retrieving data
You can also use more specific HTTP methods:
○ PUT/PATCH: Updates the specified resource
○ DELETE: Deletes the specified resource
There's nothing technically preventing you from breaking
these rules, but you should use the HTTP methods for their
intended purpose
Route params vs Query params
Generally follow these rules:
● Use route parameters for required parameters for the
request
● Use query parameters for:
○ Optional parameters
○ Parameters whose values can have spaces
These are conventions and are not technically enforced,
nor are they followed by every HTTP API
Also note that query and route parameters are all strings
Middleware
Middleware
Middlewares
Middleware
The middleware stack follows the order of
middlewares placed in the code
*Nguồn: https://iq.opengenus.org/middlewares-in-express/
Middleware
function(req, res, next) { ... };
Middleware functions can perform the following tasks:
− Execute any code
− Make changes to the request and the response objects
− End the request-response cycle
− Call the next middleware in the stack
Middleware
Types of middleware:
− Application-level middleware
− Router-level middleware
− Error handling middleware
− Built-in middleware
− External middleware (requires npm install)
Middleware
Application-level middleware: bound to the app instance
by using app.use() or app.METHOD() functions
Middleware
Application-level middleware: bound to the app instance
by using app.use() or app.METHOD() functions
Middleware
Application-level middleware
Middleware
Router-level middleware: bound to an instance of
express.Router()
Middleware
Router-level middleware: bound to an instance of
express.Router()
Middleware
Error handling middleware: defined last, after other app.use()
and routes calls
Middleware
Built-in middleware
− express.static(): serves static assets (HTML files,
images,…)
− express.json(): parses incoming requests with JSON
payloads (Express >= 4.16.0)
− express.urlencoded(): parses incoming requests with
URL-encoded payloads (Express >= 4.16.0)
Middleware
(Some) External middleware
− morgan: HTTP request logger middleware for node.js
− cors: middleware that can be used to enable CORS with
various options
− cookie-parser: parses Cookie header and populates
req.cookies with an object keyed by the cookie
− multer: middleware for handling multipart/form-data
(file uploads)
Middleware
(Some) External middleware
npm install –D morgan
const morgan = require("morgan");
app.use(morgan("dev"));
package.json
Installing dependencies
In our examples, we had to install the Express npm
packages
$ npm install express
These get written to the node_modules directory
Uploading server code
When you upload NodeJS code to a GitHub repository (or
any code repository), you should not upload the
node_modules directory:
- You shouldn't be modifying code in the node_modules
directory, so there's no reason to have it under version
control
- This will also increase your repo size significantly
Q: But if you don't upload the node_modules directory to
your code repository, how will anyone know what
libraries they need to install?
Managing dependencies
If we don't include the node_modules directory in our
repository, we need to somehow tell other people what
npm modules they need to install
npm provides a mechanism for this: package.json
package.json
You can put a file named package.json in the root directory of
your NodeJS project to specify metadata about your project
Create a package.json file using the following command:
$ npm init
This will ask you a series of questions then generate a
package.json file based on your answers
- Add -y option to get a package.json file with default values
Auto-generated package.json
Saving deps to package.json
Now when you install packages:
$ npm install express
Or
$ npm i express
An entry for this library is added in package.json
Saving deps to package.json
If you remove the node_modules directory:
$ rm -rf node_modules
You can install your project dependencies again via:
$ npm install
- This also allows people who have downloaded your code from
GitHub to install all your dependencies with one command instead
of having to install all dependencies individually
package-lock.json
package-lock.json is auto generated for any operations where
npm modifies either the node_modules tree, or package.json
It describes the exact tree that was generated
npm scripts
Your package.json file also defines scripts:
You can run these scripts using $ npm [run] scriptName
E.g. the following command runs "node server.js"
$ npm start
nodemon
Automatically restart the node application when file changes
$ npm i -D nodemon
-D option: package will appear in devDependencies section
dependencies: packages required to run
devDependencies: only for development
npm install: install packages listed in both sections
npm install --production: only packages in dependencies
are installed
nodemon
Automatically restart the node application when file changes
In package.json, use nodemon to start the server:
"scripts": {
"start": "nodemon server.js"
}
Then run $ npm start
npx: an npm package runner
npx makes it easy to use CLI tools and other executables
hosted on the registry
Using locally-installed tools without npm run-script:
$ npm i –D cowsay
$ npx cowsay hello!
Executing one-off commands:
$ npx cowsay hello!
Node.js Event Loop
Event loop
There are two types of threads in Nodejs:
− One Event Loop (aka the main loop, main thread, event
thread, etc.)
• Responsible for callbacks and non-blocking I/O (i.e.,
network I/O)
• All incoming requests and outgoing responses pass
through the Event Loop
− A pool of k (k=4 by default) Workers in a Worker Pool
(aka the threadpool)
• Handle I/O intensive tasks (dns, file APIs) or CPU-
intensive ones (crypto, zlib APIs)
Event loop
https://blog.insiderattack.net/event-loop-and-the-big-picture-nodejs-event-loop-part-1-1cb67a182810
Event loop
− All user-written synchronous JavaScript code takes priority
over async code that the runtime would like to execute
• Only after the call stack is empty does the event loop
come into play
− Every microtask queue (process.nextTick, resolved Promises)
is visited and emptied after every macrotask (setImmediate,
setTimeout,…)
Event loop
https://techva.me/callback-functions-promises-nodejs/
setTimeout
To help us understand the event loop better, let's learn
about a new command, setTimeout:
setTimeout(function, delay);
- function will fire after delay milliseconds
JS execution
Call stack: JavaScript runtime call stack. Executes the JavaScript
commands, functions
JS execution
Task Queue: When Node.js APIs notice a callback from something
like setTimeout should be fired, it creates a Task and enqueues it
in the Task Queue
JS execution
Event loop: Processes the task queues
- When the call stack is empty, the event loop pulls the next task
from the task queues and puts it on the call stack
JS execution
https://symphony.is/blog/secret-life-event-loop-meetup-overview