Below is a stripped-down segment of a webshop server with 2 APIs: ./login
to login and ./products
to show products. The products should only be shown after a succesful login.
I'm using TypeScript with Node.js and Express with session management enabled. The code below also tries to test the server by sending fetch()
requests to the APIs. NOTE: So I'm sending fetch()
requests from within Node.js, not from a browser. The code below produces the following output:
Login session ID: um2RykooJCer7Oy7YmJQs7s9ge3mZWL1
Products session ID: uAk03ochUM56rzJjPvbiRnFuPG8CUqTB
Products response: Error: Not logged in
It can be seen that the session IDs are different, that's why it fails to show products. When I leave the code running and type in the API calls manually in a browser:
http://localhost:3000/login
http://localhost:3000/products
Then the code works correctly, with the browser displaying "Login OK" and then "Apple, Orange, Pear" both with the same session ID.
So the question is: How can the server test be made to work correctly with fetch()
from Node.js, the same way as it works when called from a browser? Are there some options that can be given to the fetch()
requests or to Express? I have not found any yet.
Bonus question: Can it also be made to work with the npm package node-fetch? I use that because the built-in fetch()
from Node.js gives errors in other places.
PS I'm using the most recent versions of Node.js (v21.5), Express, express-session and node-fetch.
import Express from "express";
import Session from "express-session";
// Uncomment this to use the node-fetch module, but gives the same result:
// import fetch from "node-fetch";
declare module 'express-session' {
interface SessionData
{
loggedIn: boolean;
}
}
class App
{
express = Express();
start()
{
let session = Session( { secret: "my-secret", /* options? */ } );
this.express.use( session );
this.express.get( "/login", ( request, response ) => this.onLoginRequest( request, response ) );
this.express.get( "/products", ( request, response ) => this.onProductsRequest( request, response ) );
this.express.listen( 3000, () => this.requestLogin() );
}
// =================================== Login
requestLogin()
{
fetch( 'http://localhost:3000/login', { /* options? */ } )
.then( result => this.showProducts() );
}
onLoginRequest( request: Express.Request, response: Express.Response )
{
console.log( "Login session ID: " + request.session.id );
request.session.loggedIn = true;
response.send( "Login OK" );
}
// =================================== Products
showProducts()
{
fetch( 'http://localhost:3000/products', { /* options? */ } )
.then( result => this.onProducts( result ) );
}
onProductsRequest( request: Express.Request, response: Express.Response )
{
console.log( "Products session ID: " + request.session.id );
if( !request.session.loggedIn )
response.send( "Error: Not logged in" );
else
response.send( "Apple, Orange, Pear" );
}
onProducts( result: any )
{
result.text()
.then( ( text: string ) => console.log( "Products response: " + text ) );
}
}
( new App() ).start();
From a browser, add this option to your fetch()
call:
{ withCredentials: true }
Without that, fetch()
may not send cookies with the request. Without the cookies, there is no connection to an existing session so Express creates a new empty session on each new request.
If you're using node-fetch()
server-side, then you will need some sort of cookie jar that collects and retains cookies from one request to the next. Nodejs, unlike a browser, does not collect and keep track of the cookies for you. There a module node-fetch-cookies
that does that for node-fetch
code. You can also manually collect a returned cookie off a response and then send that cookie back with the next request (the same way a browser would).