kleydon / prisma-session-store

Express session store for Prisma
MIT License
116 stars 18 forks source link

prisma-session-store

An express session store implementation for the Prisma ORM.

Want the flexibility and scalability of a Prisma GraphQL data layer, along with the optionality and maturity of the Express ecosystem - but concerned about JWT or Paseto tokens for session management (see cautions posted here, here, here, here, and here)?

prisma-session-store simplifies access to tried-and-true express session management via Prisma's database client.

Based on: memorystore, by roccomuso

Usage

JavaScript (CommonJS)


const expressSession = require('express-session');
const { PrismaSessionStore } = require('@quixo3/prisma-session-store');
const { PrismaClient } = require('@prisma/client');

...

app.use(
  expressSession({
    cookie: {
     maxAge: 7 * 24 * 60 * 60 * 1000 // ms
    },
    secret: 'a santa at nasa',
    resave: true,
    saveUninitialized: true,
    store: new PrismaSessionStore(
      new PrismaClient(),
      {
        checkPeriod: 2 * 60 * 1000,  //ms
        dbRecordIdIsSessionId: true,
        dbRecordIdFunction: undefined,
      }
    )
  })
);

...

TypeScript

import expressSession from 'express-session';
import { PrismaSessionStore } from '@quixo3/prisma-session-store';
import  { PrismaClient } from '@prisma/client';
...

app.use(
  expressSession({
    cookie: {
     maxAge: 7 * 24 * 60 * 60 * 1000 // ms
    },
    secret: 'a santa at nasa',
    resave: true,
    saveUninitialized: true,
    store: new PrismaSessionStore(
      new PrismaClient(),
      {
        checkPeriod: 2 * 60 * 1000,  //ms
        dbRecordIdIsSessionId: true,
        dbRecordIdFunction: undefined,
      }
    )
  })
);

...

Setup

Install

Install @quixo3/prisma-session-store (and express-session, if not already installed):

NPM

$ npm install @quixo3/prisma-session-store express-session

yarn

$ yarn add @quixo3/prisma-session-store express-session

Prisma

Model

From your prisma.schema file, include a session model:

model Session {
  id        String   @id
  sid       String   @unique
  data      String   @db.MediumText  // MediumText may be needed for MySql
  expiresAt   DateTime
}

Don't forget to run npx prisma generate to generate your PrismaClient.

Types - GraphQL Nexus

If you are using @nexus/schema you can define your Session type:

...

{
  name: 'Session',
  definition(t) {
    t.model.id();
    t.model.sid();
    t.model.data();
    t.model.expiresAt();
  }
}
...

Database

If you are using Prisma's migrations you can simply run prisma migrate dev to migrate your database. If you are using something other then prisma then you will need to manage the migrations yourself and you check the Prisma Documentation on the subject if you need help.

MySQL

If you are using MySQL as your datasource provider you may also need change the type of your data column to TEXT:

USE your-database
ALTER TABLE Session MODIFY data TEXT;

Prisma String properties are mapped to VARCHAR(191) by default. Session data can be larger than 191 characters so updating the type to TEXT prevents errors when creating and updating sessions. If you know your session data will not exceed 191 characters you can skip this step or if you know your maximum size you can use VARCHAR(YOUR_MAX_SIZE)

If you are using a version of Prisma that supports migrating with native types you can use a type annotation in your schema.prisma file instead of manually modifying your data column.

Upgrading from versions following 3.1.9

Concurrent calls to set() and concurrent calls to touch() having the same session id are now disallowed by default, as a work-around to address issue 88. (This issue may surface when a browser is loading multiple resources for a page in parallel). The issue may be limited to SQLite, but hasn't been isolated; express-session or prisma may be implicated. If necessary, you can prevent the new default behavior, and re-enable concurrent calls having the same session id, by setting one or both of: enableConcurrentSetInvocationsForSameSessionID, enableConcurrentTouchInvocationsForSameSessionID to true; see below.

Migrating from versions following 2.0.0

Following version 2.0.0, the Session expires field was renamed to expiresAt to match Prisma's naming convention for date fields.

Migrating from versions prior to 1.0.0

In 1.0.0 the public API of this library was reworked. Previously the default export that was a factory to build the PrismaSessionStore class. In 1.0.0 a named export of the class PrismaSessionStore was put in place of the default export. So after updating you will need to change your import and remove your call to the factory.

JavaScript

Before

const PrismaSessionStore = require('@quixo3/prisma-session-store')(
  expressSession
);

After

const { PrismaSessionStore } = require('@quixo3/prisma-session-store');

TypeScript

Before

import prismaSessionStore from '@quixo3/prisma-session-store';

const PrismaSessionStore = prismaSessionStore(expressSession);

After

import { PrismaSessionStore } from '@quixo3/prisma-session-store';

Options

Note: If both dbRecordIdFunction and dbRecordIdIsSessionId are undefined then a random CUID will be used instead.

Five new options were added, apart from the work that was already done by memorystore, two of them relate to logging and allow you to inject your own logger object giving you flexibility to log outputs to something like NestJS or whenever you would like, even saving them to disk if that's what you want. And the third is used for testing to round the TTL so that it can be compared do another generated ttl during the assertion.

Methods

prisma-session-store implements all the required, recommended and optional methods of the express-session store, plus a few more:

Author

Krispin Leydon (kleydon), originally based on memorystore, by roccomuso, refactored to TypeScript (with extensive improvement) by wSedlacek

License

MIT