How to handle sessions properly in Express.js (with Heroku)

Tamás Polgár
Developer rants
Published in
3 min readSep 4, 2020

The default Express session storage is not designed for production environments.

Most tutorials suggest this short solution to implement session handling in a Node.js/Express application:

this.express.use(session({
secret: 'whatever',
saveUninitialized: false,
resave: false,
unset: 'destroy',
cookie: {
sameSite: 'Lax',
maxAge: 60000,
secure: true
},
}));

This works fine during development, but when you deploy it to a production one, you’ll get this console warning:

Warning: connect.session() MemoryStore is not designed for a production environment, as it will leak memory, and will not scale past a single process.

Looks scary, doesn’t it? What now? (Forget taking a deep breath and counting. Just scream. Give it out. Never mind your coworkers around you. Next time they’ll do it too. It’ll make office life much more fun and less stressful.

You have to ditch Express’ storage system and use a compatible one. This is actually what Express’ developers suggest. There are a ton of them, and they published a list for your convenience here. A database-based session handler is recommended over memory-based ones because memory can be quite finite, particularly if you expect thousands of users. (Though if you’re building an app for a very limited number of users, say, an internal application for a small company, a memory based solution will likely do just fine.)

A PostgreSQL solution

I am currently working on a PostgreSQL-based Node.js app, so I’m going to use connect-pg-simple. (This here.)

Let’s start with installing the storage handler:

npm install connect-pg-simple

This was the easy part. Connecting it to your database is a bit tricky.

The database needs to have a session storage table. I decided to add it manually because I’ve already built my database and don’t want to mess it up. The query is quite simple and it’s included in the package:

CREATE TABLE "session" (
"sid" varchar NOT NULL COLLATE "default",
"sess" json NOT NULL,
"expire" timestamp(6) NOT NULL
)
WITH (OIDS=FALSE);
ALTER TABLE "session" ADD CONSTRAINT "session_pkey" PRIMARY KEY ("sid") NOT DEFERRABLE INITIALLY IMMEDIATE;CREATE INDEX "IDX_session_expire" ON "session" ("expire");

Well, here’s a better version. This will only create the table, the constraint and the index if either doesn’t exist.

CREATE TABLE IF NOT EXISTS myschema.session (
sid varchar NOT NULL COLLATE "default",
sess json NOT NULL,
expire timestamp(6) NOT NULL,
CONSTRAINT "session_pkey" PRIMARY KEY ("sid")
);
CREATE INDEX IF NOT EXISTS "IDX_session_expire" ON myschema.session ("expire");

Now let’s add the session handler to our Express initialization.

However, let’s make two major changes to the basic example. First, we’re using Typescript again. Second, we don’t keep our database connection in the same component as Express initialization. At least I prefer to arrange such things into separate service components: one for database stuff, another for AWS S3 connection, a third one for Firebase, and so on. So in App.ts this is all we’re going to need:

import * as session from 'express-session';...this.express.use(session({
store: this.pgService.sessionHandler(session),
secret: secrets.session_secret,
saveUninitialized: false,
resave: false,
unset: 'destroy',
cookie: {
sameSite: 'Lax',
maxAge: 60000,
secure: true
}
}));

As you see, I have a method called sessionHandler() in a service I called pgService. The name implies that it’s responsible for everything related to PostgreSQL.

Here is the session handler generator:

import { Pool, Client } from 'pg';
import * as pgSession from 'connect-pg-simple';
export class PostgresqlService { private pool: new Pool({
connectionString: process.env.DATABASE_URL,
ssl: {
rejectUnauthorized: false
}
});
public sessionHandler(session) {
const pgs = pgSession(session);
return new pgs({
conString: process.env.DATABASE_URL,
pool: this.pool,
schemaName: 'myschema',
tableName: 'session',
});
}
}

What is what here? First, process.env.DATABASE_URL is where Heroku keeps the URL to the PostgreSQL database. It’s a regular connection string with the current URL, password, etc.

Pooling is an important thing on Heroku because it’s not provided for free database add-ons. If you don’t have pooling, set pool: null.

Our session handler is now working! Well, it’s supposed to. I still run into the following console error:

error: no pg_hba.conf entry for host "162.253.68.205", user "byceandifabvsp", database "d84p4nqg3q4eqv", SSL off

It can mean two things. Either your database credentials are wrong, or connect-pg-simple is attempting to connect without SSL and Heroku rejects such attempts. The latter is more likely.

Don’t worry about this error: if the credentials are OK, it will not appear after deployment. Just make sure you set secure: true in the session setup.

It may still not work locally. To bridge the problem, simply use traditional session handling in the development environment.

--

--