Using the fetch API inside Node.js
Programming Estimated reading time: ~3 minutes
I am currently developing an isomorphic web application. You can
see it in action e.g. https://erleben.enns.at/.
A few days ago it took me some time to figure out how
to be able to use the fetch
API both on the
client and the server.
Isomorphic JavaScript, also known as Universal JavaScript, describes JavaScript applications which run both on the client and the server. Why would you want such a thing? Well it’s a common approach if you want your single page application (SPA) to be also renderable by the server. So on the client the JavaScript is interpreted e.g. by Chrome or Firefox, whereas on the server it is interpreted typically by Node.js.
When you can render the HTML of your SPA on the server and send it right away to the client when it requets a page you gain certain advantages:
- Better search engine visibility (though Google Bot is able to crawl and index single page applications).
- Faster inital load times when the client requests the application for the first time. The client can dislpay the pre-rendered HTML right away. It does not have to execute some JavaScript first to load e.g. data from the backend.
We use the plain fetch API to load data from our backend. All modern browsers support this API. And for older browser polyfills such as whatwg-fetch exist.
But on Node.js there exists no such thing as the fetch
API. To be able to use fetch
inside Node.js you have to bundle a library like
node-fetch with your server bundle.
It took me some time to figure out how to get this to work in
the context of our setup: Webpack (together with the
webpack-node-externals
plugin) and Typescript.
Webpack config
These are the relevant parts of our webpack config:
var webpack = require('webpack');
const nodeExternals = require('webpack-node-externals');
const configs = (env, options) => {
return ([
{
entry: {
browser: [
// misc polyfills
'core-js/es/promise',
'core-js/es/string',
'core-js/es/typed-array',
'whatwg-fetch',
// react specifc polyfills. see
// https://reactjs.org/docs/javascript-environment-requirements.html
'core-js/es/array',
'core-js/es/map',
'core-js/es/set',
'raf/polyfill',
// the actual webapp
'./src/client.tsx',
]
},
output: {
path: __dirname + '/dist',
filename: '[name].js',
},
// Add the loader for .ts files.
module: {
rules: [
{
test: /\.tsx?$/,
loader: 'ts-loader',
options: { logLevel: 'info', transpileOnly: true }
},
],
},
},
{
entry: {
server: './src/server.tsx',
},
output: {
path: __dirname + '/dist',
filename: '[name].js',
},
// Add the loader for .ts files.
module: {
rules: [
{
test: /\.tsx?$/,
loader: 'ts-loader',
options: { logLevel: 'info', transpileOnly: true }
},
],
},
target: 'node',
externals: [nodeExternals({
whitelist: ['node-fetch']
})],
}
]);
};
module.exports = configs;
Things to note:
- In the client bundle we include a
fetch
polyfill for older browsers (whatwg-fetch
). - We tell the
webpack-node-externals
plugin not to exclude thenode-fetch
package from the server bundle.
Server initialization
When initializing the server you have to make the fetch
function
globally available as outlined below:
<...>
// enable node-fetch polyfill for Node.js
import fetch from "node-fetch";
// tslint:disable-next-line:no-any
declare var global: any;
global.fetch = fetch;
const PORT = process.env.PORT || 8080;
export const initServer = (title: string, pathToFavicon: string) => {
console.log(`Server is booting (cwd is ${process.cwd()})...`);
<...>
}).listen(PORT, () => {
console.log(`Server is now listening on ${PORT}... (${new Date()})`);
});
};
<...>
Fetch example
Finally an example of how we fetch
data from our backend services.
The fetch
API can now be used both on the client and inside
a Node.js environment.
// NOTE: You do not have to import
// or require anything to be able
// to use fetch here!
export async function fetchLocationInfo(locationId: string, proto?: string, host?: string): Promise<LocationDTO> {
const url = `<...>`;
const response = await fetch(url);
if (!response.ok) {
const httpErrorRootCause = new HttpErrorRootCause(response.status, response.statusText, await response.text());
throw new HttpStatusException(httpErrorRootCause);
}
const json = await response.json();
const obj = json as LocationDTO;
return obj;
}
A note about Netcup (advertisement)
Netcup is a German hosting company. Netcup offers inexpensive, yet powerfull web hosting packages, KVM-based root servers or dedicated servers for example. Using a coupon code from my Netcup coupon code web app you can even save more money (6$ on your first purchase, 30% off any KVM-based root server, ...).