@seanmcgary/database

Database boilerplate using Sequelize.

Usage no npm install needed!

<script type="module">
  import seanmcgaryDatabase from 'https://cdn.skypack.dev/@seanmcgary/database';
</script>

README

@seanmcgary/database

Database boilerplate using Sequelize.

Install

yarn add @seanmcgary/database

Use

Usage caveats and notes due to some sequelize weirdness:

  • Column and table names should be camelCased when used in JS/TS. Sequelize will automatically convert to/from snake_case since that is how things are represented in Postgres
  • createdAt and updatedAt columns are automatically added for each table
  • Each table will automatically get a column called id which will be a SERIAL type and set as the primary key. To override it, simple add an id column manually and give it a type.

Example directory structure and files:

.
├── package.json
├── src
│   ├── db
│   │   └── index.ts
│   ├── migrations
│   │   └── 20180407084900-example-migration.ts
│   └── models
│       └── exampleUserModel.ts
├── tsconfig.json
└── yarn.lock

4 directories, 6 files

src/db/index.ts

import { DB, MigrationConfig, DBConfig } from '@seanmcgary/database';
import * as path from 'path';

import exampleUserModel from '../models/exampleUserModel';

const databaseConfig = {
    username: 'some-username',
    password: 'some-password',
    database: 'example-db',
    host: '127.0.0.1'
};

const migrationConfig  = {
    path: path.normalize(`${__dirname}/../migrations`)
};

const db = new DB(
    <DBConfig>databaseConfig,
    <MigrationConfig>migrationConfig
);

db.init((db: DB) => {
    const User = db.setModel('User', db.loadModel(exampleUserModel));
});

db.migrate();

export default db;

src/models/exampleUserModel.ts

import { Sequelize, ModelOptions, Model, DataTypes, BuildOptions, ModelAttributes } from 'sequelize';
import { ModelWrapper, StaticModel, AllowedFields, FieldMessages } from '@seanmcgary/database';

export interface UserInstanceFields {
    password: string;
    username: string;
    email: string;
}

// tslint:disable-next-line:no-any
export interface UserInstance extends UserInstanceFields, Model {
    doesPasswordMatch(storedPassword: string, providedPassword: string): boolean;
    toJSON(sanitize?: boolean): UserInstanceFields;
}

type UserInstanceStatic = StaticModel<UserInstance>;

export class User extends ModelWrapper<UserInstance, UserInstanceStatic> {
    constructor(db: Sequelize, modelName: string, attributes: ModelAttributes, options: ModelOptions) {
        super(db, modelName, attributes, options);
    }

    get allowedFields(): AllowedFields {
        return {
            create: ['email', 'password', 'username'],
            update: ['email', 'password', 'username']
        };
    }

    get fieldMessages(): FieldMessages {
        return User.fieldMessages;
    }

    static get fieldMessages(): FieldMessages {
        return {
            email: {
                invalid: 'Please provide a valid email address'
            },
            password: {
                invalid: 'Password must be at least 8 characters'
            },
            username: {
                invalid: 'Please provide a valid username',
                unique: 'That username is already taken'
            }
        };
    }
}

export default function(db: Sequelize): User {
    return new User(db, 'users', {
        email: {
            type: DataTypes.STRING,
            allowNull: true,
            defaultValue: null,
            validate: {
                isEmail: true,
                notEmpty: true
            }
        },
        username: {
            type: DataTypes.STRING,
            allowNull: false,
            validate: {
                notEmpty: true,
                len: [1, 128]
            },
            unique: true
        },
        password: {
            type: DataTypes.STRING,
            allowNull: false,
            validate: {
                len: [8, 255]
            }
        }
    }, {
        freezeTableName: true,
        timestamps: true
    });
}