GithubHelp home page GithubHelp logo

sequelize-migration-builder's Introduction

Sequelize Migration Builder

Tests

This small module is designed to make Sequelize migrations files easier to manage by abstracting away boilerplate code that is used repeatedly and providing a builder object to construct the configuration.

// Manual Sequelize Migration

module.exports = {
  async up(queryInterface, Sequelize) {
    await queryInterface.createTable('Users', {
      id: {
        allowNull: false,
        autoIncrement: true,
        primaryKey: true,
        type: Sequelize.INTEGER,
      },
      firstName: {
        type: Sequelize.STRING,
        allowNull: false,
      },
      lastName: {
        type: Sequelize.STRING,
        allowNull: false,
      },
      email: {
        type: Sequelize.STRING,
        unique: true,
        allowNull: false,
      },
      password: {
        type: Sequelize.STRING,
        allowNull: false,
      },
    });
  },
  async down(queryInterface, Sequelize) {
    await queryInterface.dropTable('Users')
  }
}
// Sequelize Builder

const FieldBuilder = require('./utils/migration.utils');

module.exports = {
  async up(queryInterface, Sequelize) {
    await queryInterface.createTable('Users', {
      id: new FieldBuilder().primaryKey(),

      firstName: new FieldBuilder()
        .string()
        .allowNull()
        .output(),

      lastName: new FieldBuilder()
        .string()
        .allowNull()
        .output(),

      email: new FieldBuilder()
        .email()
        .allowNull()
        .unique()
        .output(),

      password: new FieldBuilder().output()
    });
  },

  async down(queryInterface, Sequelize) {
    await queryInterface.dropTable('Users');
  }
};

Installation

The module is available via either directly targeting the github repository in your package.json or by setting up a custom registery using the .npmrc file.

Github

If you are going to install the github repository directly, you can use the following command:

npm install --save-dev https://github.com/tkottke90/sequelize-migration-builder

NPM

The module is hosted in Github Packages. To install using npm, first you will need to create a Personal Access Token which will be used by npm to authenticate to Github and download the package.

You can create a token by following the Github Documentation. When creating the token you will specifically need to include the read:packages permission which allows the token to be used to download from Github.

Next you will need to create a .npmrc file in your project, add your personal access token and define the where Github can find the module:

echo "//npm.pkg.github.com/:_authToken=<Personal Access Token>" >> /.npmrc
echo "@tkottke90:registry=https://npm.pkg.github.com\n" >> .npmrc

This will let NPM know that when you are looking for packages in the @tkottke90 scope, to look in github packages instead of in NPM. Then install the module using NPM:

npm install --save-dev @tkottke90/[email protected]

Usage

To use the module, simply import it into your migration files. You can then create a new instance of the builder for each column you wish to add:

// Sequelize Builder

const FieldBuilder = require('./utils/migration.utils');

module.exports = {
  async up(queryInterface, Sequelize) {
    await queryInterface.createTable('Users', {
      id: new FieldBuilder().primaryKey(),

      firstName: new FieldBuilder()
        .string()
        .required()
        .output(),
  },

  async down(queryInterface, Sequelize) {
    await queryInterface.dropTable('Users');
  }
};

At the core each column should instantiate a new instance of the FieldBuilder class.

firstName: new FieldBuilder()

Once you have the class, you will have access to all the functions available to that class (see docs below for full list). For example, calling the string() and optional() will add the Sequelize.STRING type and set the optional property to false which results in a required string field:

firstName: new FieldBuilder()
  .string()
  .optional()

Finally call the output method to serialize to output the data to the property;

firstName: new FieldBuilder()
  .string()
  .optional()
  .output()


// Output:
firstName: {
  type: Sequelize.STRING
  default: "",
  allowNull: false
}

Implementation Notes

Under the hood the main action being taken by the builder is to use Object.assign to update the internal state of the instance. For this reason, the order of your functions can be important.

Motivation

I found it tedious to have to manage Sequelize migration implementation by hand since many of the column configurations used the exact same objects in the end. Additionally I wanted to explore the builder pattern on a small scale to better understand it's implementation. I was especially interested in the a la carte style function chaining allowing the developer using the module to build something up by calling specific methods. I have seen this pattern used in other modules like Joi and MomentJS which inspired the investigation.

Column Type Functions

The following functions configure a column by setting the type property on the column. Some functions will set additional supporting properties such as the enumColumn function which will populate an array of accepted values.

Boolean Column

Marks a column for storing boolean values.

Usage: FieldBuilder.boolean()

Example:

field: FieldBuilder.boolean();

// Output
// field: {
//   type: Sequelize.BOOLEAN
// }

Date Column

Marks a column for storing date values. Note that this is the simplest date option provided by Sequelize. Other date based types can be found in their documentation.

Usage: FieldBuilder.date()

Example:

field: FieldBuilder.date();

// Output
// field: {
//   type: Sequelize.DATE
// }

Email Column

Marks the column as a string and assigns the isEmail validation condition. This is one of the many validators provided by sequelize

Usage: FieldBuilder.email()

Example:

field: FieldBuilder.email();

// Output
// field: {
//   type: Sequelize.STRING,
//   validate: {
//      isEmail: true,
//    },
//  }

Enum Column (Postgres, MySQL, MariaDB only)

Configures a column to accept a set of accepted values. This is a list of strings either as a list of parameters or as an array of strings.

Usage: FieldBuilder.enum(...options: string)

Example:

field: FieldBuilder.enum('pending', 'active', 'complete');

// Output
// field: {
//   type: Sequelize.ENUM,
//   values: [ 'pending', 'active', 'complete' ],
//  }
const status = ['pending', 'active', 'complete']
field: FieldBuilder.enum(status);

// Output
// field: {
//   type: Sequelize.ENUM,
//   values: [ 'pending', 'active', 'complete' ],
//  }

Foreign Key Column

Creates a foreign key constraint to tie this column to the key of another table.

Usage: FieldBuilder.foreignKey(model: string, key?: string)

Example:

// Ties this table to another table called `Addresses`

field: FieldBuilder.foreignKey('Addresses');

// Output
// field: {
//    type: Sequelize.INTEGER
//    references: {
//      model: {
//        tableName: 'Addresses'
//      }
//      key: 'id'
//    }
//  }
// Ties this table to another table called `Users_Addresses` on the column named `userId`

field: FieldBuilder.foreignKey('Users_Addresses', 'userId);

// Output
// field: {
//    type: Sequelize.INTEGER
//    references: {
//      model: {
//        tableName: 'Addresses'
//      }
//      key: 'userId'
//    }
//  }

JSON Column (Postgres, MySQL, MariaDB only)

Marks a column for storing JSON data. Full details can be found in the documentation.

Usage: FieldBuilder.json(defaultValue?: Record<string,any>)

Example:

field: FieldBuilder.json();

// Output
// field: {
//   type: Sequelize.JSON
//   default: {}
// }

Number Column

Marks a column for various types of numbers.

Usage: FieldBuilder.number(type?: string)

Example:

field: FieldBuilder.number();

// Output
// field: {
//   type: Sequelize.INTEGER
// }

In addition to paramter-less, the function can also take a single parameter to assign various other number data types. The list here is selected based on the Sequelize documentation by selecting the configurations with the most support. If you wish to use another type of number column, use the custom input function.

Type Output
'bigint' Sequelize.BIGINT
'double' Sequelize.DOUBLE
'float' Sequelize.FLOAT
'integer' Sequelize.INTEGER
'smallint' Sequelize.SMALLINT

Example:

field: FieldBuilder.number('double');

// Output
// field: {
//   type: Sequelize.DOUBLE
// }

Primary Key Column

Configures the column as the primary key for the record. This includes setting the auto increment flag and identifying the column as the primary key.

Usage: FieldBuilder.primaryKey()

Example:

field: FieldBuilder.primaryKey();

// Output
// field: {
//    allowNull: false,
//    autoIncrement: true,
//    primaryKey: true,
//    type: Sequelize.INTEGER,
//  }

String Column

Marks a column for storing smaller strings. Per the documentation, Sequelize will create a VARCHAR type with a 255 character max.

Usage: FieldBuilder.string()

Example:

field: FieldBuilder.string();

// Output
// field: {
//   type: Sequelize.STRING
// }

Text Column

Marks a column for storing larger strings. Depending on your database, Sequelize will use that flavor syntax to generate the field. The max size will depend on the database and it is recommended that you review the documentation.

Usage: FieldBuilder.text()

Example:

field: FieldBuilder.text();

// Output
// field: {
//   type: Sequelize.TEXT
// }

Modifier Functions

The following functions are available on the FieldBuilder class instance and will apply specific configurations to the field

Custom Input

Allows for custom configuration not provided by this tool. This function will throw an error if you attempt to pass a non-object.

Usage: FieldBuilder.customInput(input: Record<string, any>)

Example:

// Add a JSONB field to a PostgreSQL table

FieldBuilder.customInput({ type: Sequelize.JSONB });

// Output
// {
//   type: Sequelize.JSONB
// }
// Invalid: Passing a non-object

FieldBuilder.customInput(true);

// Output
// Error: Invalid custom input

Default Value

Configures a default value for the field. Note that this is not type checked against the fields type value.

Usage: FieldBuilder.defaultValue(value: any)

Example:

// Set default value on a string column to `None`

FieldBuilder
  .string()
  .defaultValue('None');

// Output
// {
//   type: Sequelize.JSONB
// }
// Invalid: Parameter-less call

FieldBuilder.defaultValue();

// Output
// Error: Default value must be defined

Non-Empty String

Adds the notEmpty validation flag to the fields validate configuration.

Usage: FieldBuilder.nonEmptyString()

Example:

field: new FieldBuilder().nonEmptyString();

// Output
// field: {
//  validate: {
//    notEmpty: true
//  }
// }

Optional Column

Sets the field as allowing NULL as a value. Depending on your SQL dialect, you may or may not need this. Check your dialect documentation for more details.

Example:

field: new FieldBuilder().optional();

// Output
// field: {
//  allowNull: true
// }

Required Column

Sets the field as requiring a non-null value. Depending on your SQL dialect, you may or may not need this. Check your dialect documentation for more details.

Example:

field: new FieldBuilder().required();

// Output
// field: {
//  allowNull: false
// }

Status Columns

This is a special static method that I have added to the builder to further remove the boilerplate of having to setup tables. In each of my tables I have included an active, created_at, and updated_at columns. This method can be used to apply those columns to your table if you would like.

Usage: FieldBuilder.statusColumns()

Example:

up(queryInstance, Sequelize) {
  await queryInterface.createTable('Users', {
    id: new FieldBuilder().primaryKey().output();

    ...FieldBuilder.statusColumns()
  });
}

// Output:
//  {
//    id: {
//      allowNull: false,
//      autoIncrement: true,
//      primaryKey: true,
//      type: Sequelize.INTEGER,
//    },
//    active: { type: Sequelize.BOOLEAN },
//    created_at: { type: Sequelize.DATE },
//    updated_at: { type: Sequelize.DATE }
//  }

If you wish to rename the columns, you an do so using normal Javascript techniques like destructuring:

const { active, created_at, updated_at } = FieldBuilder.statusColumns();


up(queryInstance, Sequelize) {
  await queryInterface.createTable('Users', {
    id: new FieldBuilder().primaryKey();

    createdTimestamp: created_at,
    updatedTimestamp: updated_at
  });
}

Unique

The unique operator allows you to identify a column as unique to the table. This will be set as a constraint in your SQL database and will be enforced by the database.

There are 2 forms of uniqueness. The first one is column level uniqueness. To implement column level uniqueness, simply call the unique function:

field: new FieldBuilder().unique();

// Output:
//  {
//    unique: true
//  }

Alternatively you can create named constraints by passing a string constraint name:

field: new FieldBuilder().unique('user_info');

// Output:
//  {
//    unique: 'user_info'
//  }

Named constraints can come in handy when you are building out tables that require that multiple pieces of information are unique in conjunction with each other:

up(queryInstance, Sequelize) {
  await queryInterface.createTable('Users', {
    id: new FieldBuilder().primaryKey();

    name: new FieldBuilder().string().unique('user_info')
    email: new FieldBuilder().email().unique('user_info')
  });
}

// Output
// {
//   id: {
//     allowNull: false,
//     autoIncrement: true,
//     primaryKey: true,
//     type: Sequelize.INTEGER,
//   },
//   name: {
//     type: Sequelize.STRING,
//     default: '',
//     unique: 'user_info'
//   },
//   email: {
//     type: Sequelize.STRING,
//     validate: {
//       isEmail: true
//     },
//     unique: 'user_info'
//   }
// }

Feedback

If you have feedback, don't hesitate to open an issue and I will get to it when I have time.

sequelize-migration-builder's People

Contributors

tkottke90 avatar

Watchers

James Cloos avatar Steven Beshensky avatar  avatar

sequelize-migration-builder's Issues

Test Suite - Insufficient Testing

Description:

After removing the output() method requirement from the module in v2 I found that there was an issue with the implementation in which the class instance was not serializing the class instance into JSON to be consumed by the migration tool.

This error came up when running migrations:

syntax error at or near "["

Looking at the raw SQL query it looks like it is trying to Create the table with [object Object instead of parsing that into a string.

My theory is the solution lies in using the Umzug module to generate mock migrations in the test suite and checking that the raw SQL query

Allow Null defaults to false

The allowNull function currently defaults to false. Using the allowNull function and not passing it true is a bit pointless. I'd suggest removing the boolean parameter all-together and just making allowNull a function that makes a column nullable without parameter.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.