5. The HTTP Functions of TypeScript
Here we present two ways to make HTTP requests in TypeScript: the [fetch] function, which has been native to Node since version 18, and the third-party library [axios]. The scripts for this chapter are located in the [http] folder of the project.

In 2019, fetch had not yet been implemented by node.js, and the original document recommended the node-fetch package. Since Node 18 (2022), [fetch] has been a native global function, standardized by the WHATWG Fetch standard—no longer needs to be imported.
[axios] remains an interesting library, however: compatible with both node.js and browsers, it offers a slightly richer API (interceptors, automatic JSON serialization, standardized error handling). We present both, using the same example, to show that the coding approach is similar.
5.2. The Tax Calculation Server
This chapter, like the following ones, refers to the NestJS server installed earlier. The test architecture is as follows:
In [A-B], JavaScript code executed by [node.js] will send requests to the web application [C] via [1]. The web application will send back jSON responses (4). The client-side JavaScript code can be organized into layers [UI, business, dao] (UI: User Interface). We will do this later. To run the following scripts, your NestJS server must be running.
5.3. [fetch-01] script
This script uses [fetch] to initialize a JSON session with the tax calculation server:
| 'use strict';
// [2026 update] in 2019, you had to install the [node-fetch] package to have
// the [fetch] function on the node side. Since Node 18 (2022), [fetch] is a native global
// function, standardized by the "WHATWG Fetch standard": no need to import it anymore.
// imports
import qs from 'qs';
import { sprintf } from 'sprintf-js';
import moment from 'moment';
// base URL of the tax-calculation server
const baseUrl: string = 'http://localhost:3000/main.php?';
// init session
async function initSession(): Promise<any> {
// [2026 update] the native [fetch] does not know the [timeout] option.
// to limit the waiting time, we give it a [signal] produced by
// [AbortSignal.timeout(ms)] (available since Node 17.3): after this delay,
// the request is automatically cancelled and [fetch] throws an error.
const options: RequestInit = {
method: "GET",
signal: AbortSignal.timeout(2000)
};
// execution of the HTTP request [get /main.php?action=init-session&type=json]
let fetchStart: moment.Moment | undefined;
try {
// asynchronous request - [fetch] returns a promise
fetchStart = moment(Date.now());
const response = await fetch(baseUrl + qs.stringify({
action: 'init-session',
type: 'json'
}), options);
// [response] is the whole HTTP response of the server (HTTP headers + the response itself)
// we display this response to see its structure
console.log(sprintf("fetch response formatted as json,=%j, %s", response, time(fetchStart)));
console.log("fetch response in javascript=", response);
// we can access the HTTP headers
console.log("response headers=", response.headers);
// if the response is of type application / json, the server's json response is obtained with the asynchronous function [response.json()]
// in this case the calling code gets a [Promise] object
// [await] makes it possible to get the server's [json] response rather than its promise
const jsonStart = moment(Date.now());
const object = await response.json();
console.log(sprintf("json response=%j, type=%s, %s", object, typeof (object), time(jsonStart)));
return object;
// if the response is of type text / plain, the server's text response is obtained with [response.text()]
// in this case the calling code gets a [Promise] object
// [await] makes it possible to get the server's [text] response rather than its promise
// const text = await response.text();
// console.log("text response=", text);
// return text;
} catch (error: any) {
// we are here because the server sent an error code [404 Not Found, ...] with an empty body - we display the error to see its structure
// or because the [fetch] client threw an exception (network unreachable, ...)
// we display the structure of the error
console.log(sprintf("fetch error in json=%j, %s", error, time(fetchStart)));
console.log("fetch error in javascript=", typeof (error), error);
// we throw the error msg received
throw error.message;
}
}
// the main function executes the asynchronous function [initSession]
async function main(): Promise<void> {
try {
console.log("HTTP request to the server in progress ---------------------------------------------");
const response = await initSession();
console.log("success ---------------------------------------------");
console.log("response=", response, typeof (response))
} catch (error) {
console.log("error ---------------------------------------------");
console.log("error=", error, typeof (error));
}
}
// test
main();
// utility displaying time and duration
function time(start?: moment.Moment): string {
// time of the current moment
const now = moment(Date.now());
// time formatting
let result = "time=" + now.format("HH:mm:ss:SSS");
// should a duration be calculated?
if (start) {
const duration = now.valueOf() - start.valueOf();
const milliseconds = duration % 1000;
const seconds = Math.floor(duration / 1000);
// time + duration formatting
result = result + sprintf(", duration= %s second(s) and %s milliseconds", seconds, milliseconds);
}
// result
return result;
}
|
- lines 3–5: [2026 update] — comment noting that
fetch is now native, unlike in 2019;
- line 13: this line is important. It specifies the header for the URL that the script will query. This will be supplemented by any parameters. Here, we used the configuration defined in the section “Installing a NestJS server”;
- line 22: AbortSignal.timeout(2000) — replaces the timeout option in node-fetch (which does not exist in native fetch): if the request takes longer than 2 seconds, it is automatically canceled;
- line 29: fetch(URL, options) — the syntax remains that of standard fetch: a URL, and an options object (method, cancellation signal, etc.);
- lines 30–31: a jSON session is initiated;
- line 36: the entire server response is displayed;
- line 38: the response headers are displayed;
- line 43: await response.json() — extracts the JSON body from the response; this is itself an asynchronous operation (the response may be a stream still being received);
- line 52:
fetch only throws an exception if the HTTP request failed (network unreachable, timeout, etc.). If a response is received, even with an error code (400, 500, etc.), fetch does not throw an exception: the response is normally available, line 38.
Execution result:
| HTTP request to the server in progress ---------------------------------------------
fetch response formatted as json,={}, time=12:36:31:319, duration= 0 second(s) and 93 milliseconds
fetch response in javascript= Response {
status: 200,
statusText: 'OK',
headers: Headers {
date: 'Sun, 06 Sep 2026 10:36:31 GMT',
server: 'Apache/2.4.66 (Win64) OpenSSL/3.0.18 PHP/8.3.30',
'x-powered-by': 'PHP/8.3.30',
'cache-control': 'max-age=0, private, must-revalidate, no-cache, private',
'set-cookie': 'PHPSESSID=9u1rb9euj0m6f18efm7b5m952t; path=/',
'content-length': '84',
connection: 'close',
'content-type': 'application/json'
},
body: ReadableStream { locked: false, state: 'readable', supportsBYOB: true },
bodyUsed: false,
ok: true,
redirected: false,
type: 'basic',
url: 'http://localhost/main.php?action=init-session&type=json'
}
response headers= Headers {
date: 'Sun, 06 Sep 2026 10:36:31 GMT',
server: 'Apache/2.4.66 (Win64) OpenSSL/3.0.18 PHP/8.3.30',
'x-powered-by': 'PHP/8.3.30',
'cache-control': 'max-age=0, private, must-revalidate, no-cache, private',
'set-cookie': 'PHPSESSID=9u1rb9euj0m6f18efm7b5m952t; path=/',
'content-length': '84',
connection: 'close',
'content-type': 'application/json'
}
json response={"action":"init-session","status":700,"response":"session started with type [json]"}, type=object, time=12:36:31:323, duration= 0 second(s) and 2 milliseconds
success ---------------------------------------------
response= {
action: 'init-session',
status: 700,
response: 'session started with type [json]'
} object
|
- Lines 3–22: the entire server response;
- line 4: the 200 status code means that the server was able to process the requested URL. This is shown on line 21. We have already used this URL with the Postman client;
- lines 23–32: the response headers. They provide information about the response that follows. For example, on line 29, the server indicates that this response is 84 characters long;
- line 28: the server sent a session cookie. This allows a client to maintain a memory space on the server called a session. This is managed by the web server. After receiving this session cookie—which is actually the client’s identifier with the server—the client must send it back with every new request. Note the format of this session cookie. We will retrieve it shortly in a TypeScript client.
- line 33: the server’s jSON response;
5.4. script [fetch-02]
This script reuses fetch-01 while removing all demonstration details that are unnecessary for everyday use:
| 'use strict';
// [2026 update] [fetch] is now a native global function of node
// (since Node 18): no need to import the [node-fetch] package anymore as in 2019.
// imports
import qs from 'qs';
// base URL of the tax-calculation server
const baseUrl: string = 'http://localhost:3000/main.php?';
// init session
async function initSession(): Promise<any> {
// options of the HHTP request [get /main.php?action=init-session&type=json]
// [AbortSignal.timeout(ms)] replaces the [timeout] option which does not exist on native fetch
const options: RequestInit = {
method: "GET",
signal: AbortSignal.timeout(2000)
};
// execution of the HTTP request [get /main.php?action=init-session&type=json]
const response = await fetch(baseUrl + qs.stringify({
action: 'init-session',
type: 'json'
}), options);
// result received in jSON
return await response.json();
}
// the main function executes the asynchronous function [initSession]
async function main(): Promise<void> {
try {
console.log("HTTP request to the server in progress ---------------------------------------------");
const response = await initSession();
console.log("success ---------------------------------------------");
console.log("response=", response)
} catch (error: any) {
console.log("error ---------------------------------------------");
console.log("error=", error.message);
}
}
// test
main();
|
Execution result:
| HTTP request to the server in progress ---------------------------------------------
success ---------------------------------------------
response= {
action: 'init-session',
status: 700,
response: 'session started with type [json]'
}
|
5.5. script [axios-01]
Same example, with [axios]:
| 'use strict';
import axios, { AxiosInstance, AxiosRequestConfig } from 'axios';
// axios default configuration
axios.defaults.timeout = 2000;
axios.defaults.baseURL = 'http://localhost:3000/';
// init session
async function initSession(axios: AxiosInstance): Promise<any> {
// options of the HHTP request [get /main.php?action=init-session&type=json]
const options: AxiosRequestConfig = {
// [TypeScript update] axios.request() accepts, as far as typing is concerned, only one
// configuration argument: the URL must therefore be in [options.url]
// rather than as a separate 1st parameter of request()
url: 'main.php',
method: "GET",
// URL parameters
params: {
action: 'init-session',
type: 'x'
}
};
// execution of the HTTP request [get /main.php?action=init-session&type=json]
try {
// asynchronous request
const response = await axios.request(options);
// response is the whole HTTP response of the server (HTTP headers + the response itself)
// we display this response to see its structure
// console.log("axios response=", response);
// the server's response is in [response.data]
return response.data;
} catch (error: any) {
// we are here because the server sent an error code [404 Not Found, 500 Internal Server Error, ...]
// the [error] parameter is an exception instance - it can take various forms
// we display it to see its structure
// console.log("axios error=", typeof (error), error);
if (error.response) {
// the server reported an error in the HTTP status but it also sent a response
// which is then found in [error.response.data]
// we know that the server sends jSON responses with the structure {action, status, response}
// and that in case of error, the error msg is in [response]
return error.response.data;
} else {
// we throw the error
throw error;
}
}
}
// the main function executes the asynchronous function [initSession]
async function main(): Promise<void> {
try {
console.log("HTTP request to the server in progress ---------------------------------------------");
const response = await initSession(axios);
console.log("success ---------------------------------------------");
console.log("response=", response, typeof (response))
} catch (error: any) {
console.log("error ---------------------------------------------");
console.log("error=", error.message);
}
}
// test
main();
|
- The URL query is constructed from the information in lines 6, 15, 19, and 20;
- line 26: axios.request(options) — unlike
fetch, the URL and the options are grouped into a single configuration object (line 11);
- line 37: error.response — if the server responded (even with an error code), Axios places this response in error.response, which is accessible in the
catch block — unlike fetch, Axios always throws an exception whenever the status code is not a success (2xx);
- line 31: the successful response is in response.data — axios automatically extracts JSON; there’s no need for a separate await response.json() as with fetch.
Execution result:
| HTTP request to the server in progress ---------------------------------------------
success ---------------------------------------------
response= {
action: 'init-session',
status: 700,
response: 'session started with type [json]'
} object
|
5.6. [axios-03] script — authentication
This script chains two requests: init-session, followed by authenticate-user in **[POST], with a body encoded by the **[qs] library:
| 'use strict';
import axios, { AxiosInstance, AxiosRequestConfig } from 'axios';
import qs from 'qs'
// axios configuration
axios.defaults.timeout = 2000;
axios.defaults.baseURL = 'http://localhost:3000/';
// init session
async function initSession(axios: AxiosInstance): Promise<any> {
// options of the HHTP request [get /main.php?action=init-session&type=json]
const options: AxiosRequestConfig = {
// [TypeScript update] URL included in [options.url] (the typed axios.request() accepts only one argument)
url: 'main.php',
method: "GET",
// URL parameters
params: {
action: 'init-session',
type: 'json'
}
};
try {
// execution of the HTTP request [get /main.php?action=init-session&type=json]
const response = await axios.request(options);
// the server's response is in [response.data]
return response.data;
} catch (error: any) {
// server response
if (error.response) {
// the jSON response is in [error.response.data]
return error.response.data;
} else {
// we rethrow the error
throw error;
}
}
}
async function authenticateUser(axios: AxiosInstance, user: string, password: string): Promise<any> {
// options of the HHTP request [post /main.php?action=authenticate-user]
const options: AxiosRequestConfig = {
// [TypeScript update] URL included in [options.url]
url: 'main.php',
method: "POST",
headers: {
'Content-type': 'application/x-www-form-urlencoded',
},
// POST body
data: qs.stringify({
user: user,
password: password
}),
// URL parameters
params: {
action: 'authenticate-user'
}
};
try {
// execution of the HTTP request [post /main.php?action=authenticate-user]
const response = await axios.request(options);
// the server's response is in [response.data]
return response.data;
} catch (error: any) {
// server response
if (error.response) {
// the jSON response is in [error.response.data]
return error.response.data;
} else {
// we rethrow the error
throw error;
}
}
}
// the main function executes the asynchronous functions one by one
async function main(): Promise<void> {
try {
// init-session
console.log("init-session action in progress ---------------------------------------------");
const response1 = await initSession(axios);
console.log("success ---------------------------------------------");
console.log("response=", response1);
// authenticate-user
console.log("authenticate-user action in progress ---------------------------------------------");
const response2 = await authenticateUser(axios, 'admin', 'admin');
console.log("success ---------------------------------------------");
console.log("response=", response2)
} catch (error) {
console.log("error ---------------------------------------------");
console.log("error=", error);
}
}
// test
main();
|
- lines 42–53: The POST request sends its parameters (user, password), encoded as application/x-www-form-urlencoded, via qs.stringify(...) in options.data;
- lines 55–57: the URL ([post /main.php?action=authenticate-user]) also has a parameter;
- both functions are executed sequentially in
main (awaited one after the other) (lines 81, 86): the second can only make sense if the first succeeded, since it depends on the session cookie set by init-session.
Execution result:
| init-session action in progress ---------------------------------------------
success ---------------------------------------------
response= {
action: 'init-session',
status: 700,
response: 'session started with type [json]'
}
authenticate-user action in progress ---------------------------------------------
success ---------------------------------------------
response= {
action: 'authenticate-user',
status: 103,
response: [ 'no session in progress. Start with action [init-session]' ]
}
|
- Lines 10–14: The action [authenticate-user] failed. The reason is as follows:
- The action [init-session] did not send the session cookie to the server. This is the client’s identifier with the server. Because it did not send this identifier, the client is unknown to the server. In its response, the server included a session cookie. This tells the client: “Here is your identifier for your future requests”;
- but the action [authenticate-user] also failed to send the session cookie to the server. This is because [init-session] did not take the time to store it. The server therefore does not recognize the client and does not know that it previously initiated a jSON session, and it informs the client of this;
The solution to this problem is to manage the session cookie sent by the server:
- The very first request made to the server must store the session cookie sent by the server;
- subsequent requests must retrieve this session cookie and send it to the server to authenticate;
5.7. Script [axios-04] — session cookie management
The code is the same as before but adds a function, [getRemoteData], to manage the session cookie:
| 'use strict';
import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios';
import qs from 'qs'
// axios configuration
axios.defaults.timeout = 2000;
axios.defaults.baseURL = 'http://localhost:3000/';
// session cookie
const sessionCookieName: string = "connect.sid";
let sessionCookie: string = '';
// init session
async function initSession(axios: AxiosInstance): Promise<any> {
// options of the HHTP request [get /main.php?action=init-session&type=json]
const options: AxiosRequestConfig = {
// [TypeScript update] URL included in [options.url]
url: 'main.php',
method: "GET",
// URL parameters
params: {
action: 'init-session',
type: 'json'
}
};
// execution of the HTTP request
return await getRemoteData(axios, options);
}
async function authenticateUser(axios: AxiosInstance, user: string, password: string): Promise<any> {
// options of the HHTP request [post /main.php?action=authenticate-user]
const options: AxiosRequestConfig = {
// [TypeScript update] URL included in [options.url]
url: 'main.php',
method: "POST",
headers: {
'Content-type': 'application/x-www-form-urlencoded',
},
// POST body
data: qs.stringify({
user: user,
password: password
}),
// URL parameters
params: {
action: 'authenticate-user'
}
};
// execution of the HTTP request
return await getRemoteData(axios, options);
}
async function getRemoteData(axios: AxiosInstance, options: AxiosRequestConfig): Promise<any> {
// for the session cookie
if (!options.headers) {
options.headers = {} as any;
}
(options.headers as any).Cookie = sessionCookie;
// execution of the HTTP request
let response: AxiosResponse;
try {
// asynchronous request
response = await axios.request(options);
} catch (error: any) {
// the [error] parameter is an exception instance - it can take various forms
if (error.response) {
// the server's response is in [error.response]
response = error.response;
} else {
// we rethrow the error
throw error;
}
}
// response is the whole HTTP response of the server (HTTP headers + the response itself)
// we retrieve the session cookie if it exists
const setCookie = response.headers['set-cookie'];
if (setCookie) {
// setCookie is an array
// we look for the session cookie in this array
let found = false;
let i = 0;
while (!found && i < setCookie.length) {
// we look for the session cookie
const results = RegExp('^(' + sessionCookieName + '.+?);').exec(setCookie[i]);
if (results) {
// we store the session cookie
// eslint-disable-next-line require-atomic-updates
sessionCookie = results[1];
// found it
found = true;
} else {
// next element
i++;
}
}
}
// the server's response is in [response.data]
return response.data;
}
// the main function executes the asynchronous functions one by one
async function main(): Promise<void> {
try {
// init-session
console.log("init-session action in progress ----------------");
const response1 = await initSession(axios);
console.log("success --------------------------");
console.log("response=", response1);
// authenticate-user
console.log("authenticate-user action in progress ---------");
const response2 = await authenticateUser(axios, 'admin', 'admin');
console.log("success -------------------");
console.log("response=", response2)
} catch (error: any) {
console.log("error ------------------");
console.log("error=", error.message);
}
}
// test
main();
|
This script factors initSession and authenticateUser around a common function, [getRemoteData], which explicitly handles the session cookie (here connect.sid, see line 10)—which is essential because the server associates authentication and simulations with an HTTP session:
- line 10: the name of the session cookie being searched for. It depends on the web server used. The NestJS server used in this course uses a session cookie named [connect.sid];
- line 53: the [getRemoteData] function handles sending requests and receiving the server’s response. It also manages the session cookie. It takes two parameters:
- the [AxiosInstance] object, which handles the general mechanics of HTTP Axios requests. This object and others are obtained through the import in line 2;
- the [AxiosRequestConfig] object, which defines the options for a specific request;
- Lines 54–58: [options.headers] sets the header for the HTTP request that will be sent to the server. If a session cookie exists, it must be included in this header. Line 58 performs this task. Upon completion of the first request, [getRemoteData] will store this cookie in the global variable defined on line 11. During the first request, its value will be an empty string. For subsequent requests, it will be the character string of the session cookie sent by the server in response to the first request.
- Line 63: The HTTP request is executed;
- line 76: the response header is checked to see if there is a “set-cookie” line. This line is present only if the client’s previous request did not include a “Cookie” line in its headers. When the server receives a HTTP request from the client that includes the ‘Cookie’ line, it no longer sends the session cookie because it knows the client already has it. It is up to the client to resend it with each new request. This is what happens on line 58;
- line 77: if the server sent the header [Set-Cookie], then its contents must be retrieved. This header may contain multiple cookies in the form [cookie_name=value]. Lines 77–95 will be executed only once during the script’s execution. As mentioned, the server does not send the session cookie in response to requests that have been authenticated with that same session cookie;
- line 84: we explicitly search for the cookie whose name was stored on line 10;
- line 88: if it is found, it is stored in the global variable defined on line 11;
This mechanism ensures that:
- the session cookie is retrieved from the response to the first HTTP request;
- it is then systematically sent to the server with each new request, allowing the server to recognize its client;
The code is executed:
Result of execution:
| init-session action in progress ----------------
success --------------------------
response= {
action: 'init-session',
status: 700,
response: 'session started with type [json]'
}
authenticate-user action in progress ---------
success -------------------
response= {
action: 'authenticate-user',
status: 200,
response: 'Authentication successful [admin, admin]'
}
|
5.8. script [fetch-03] — canceling requests
[NEW since 2019] This script illustrates two uses of the mechanism for canceling fetch requests, available natively since Node 18:
| 'use strict';
// ========================================================================
// [NEW since 2019] native fetch + cancellation of an HTTP request
// ========================================================================
// [fetch] is global since Node 18: no need for the [node-fetch] package
// used in 2019 in fetch-01.js/fetch-02.js (see the [2026 update] comments
// added in these two files)
// base URL of the tax-calculation server (see http/README.md)
// as for axios-01.js, this script assumes that the course's PHP server is running
// locally: without it, the requests fail with "fetch failed", which is normal
const baseUrl: string = 'http://localhost:3000/main.php';
// example 1: request with a maximum delay thanks to AbortSignal.timeout()
async function requestWithMaximumDelay(): Promise<void> {
console.log("--- request with AbortSignal.timeout ---");
try {
const response = await fetch(baseUrl + '?action=init-session&type=json', {
// beyond 2 seconds, the request is cancelled automatically
signal: AbortSignal.timeout(2000)
});
const data = await response.json();
console.log("response =", data);
} catch (error: any) {
// if the delay is exceeded,
// [error.name] is "TimeoutError" or "AbortError" depending on the case
console.log("error.name =", error.name, ", message =", error.message);
}
}
// example 2: manual cancellation of a request, for example because
// the user changed their mind (e.g.: they retype in a search field)
async function manuallyCancellableRequest(): Promise<void> {
console.log("--- manually cancelled request ---");
const controller = new AbortController();
// we schedule a "business" cancellation after 500 ms
// (in a real application, this would be for example a click on a "cancel" button)
setTimeout(() => {
console.log("manual cancellation triggered");
controller.abort();
}, 10);
try {
const response = await fetch(baseUrl + '?action=init-session&type=json', {
signal: controller.signal
});
const data = await response.json();
console.log("response =", data);
} catch (error: any) {
console.log("error.name =", error.name, ", message =", error.message);
}
}
async function main(): Promise<void> {
await requestWithMaximumDelay();
await manuallyCancellableRequest();
}
main();
|
- [AbortSignal.timeout(ms)]: automatically cancels the request if it exceeds the specified timeout—a pattern already seen in the fetch-01 script;
- [AbortController]: allows for manual cancellation, triggered by any application event (here, a setTimeout simulating, for example, a click on a “Cancel” button, or new text entered into a search field that invalidates the previous request).
Execution result:
| --- request with AbortSignal.timeout ---
response = {
action: 'init-session',
status: 700,
response: 'session started with type [json]'
}
--- manually cancelled request ---
manual cancellation triggered
error.name = AbortError , message = This operation was aborted
|
- Lines 2–6: The first request completed normally;
- lines 8–9: the second request was canceled due to the very short timeout (10 ms) set for the request (line 43 of the code);