<img height="1" width="1" style="display:none" src="https://www.facebook.com/tr?id=1063935717132479&amp;ev=PageView&amp;noscript=1 https://www.facebook.com/tr?id=1063935717132479&amp;ev=PageView&amp;noscript=1 "> Bitovi Blog - UX and UI design, JavaScript and Front-end development

Battle of the Node.js ORMs: Objection vs. Prisma vs. Sequelize

Nauany Costa

Which Node.js ORM is the best tool for your backend project? Weigh the pros and cons of Objection, Prisma, and Sequelize and choose your ORM champion!

posted in Development, Node.js, SQL, Nodejs Consulting on July 22, 2022 by Nauany Costa


Battle of the Node.js ORMs: Objection vs. Prisma vs. Sequelize

Nauany Costa by Nauany Costa

You know them, you love them, but when you have 3,524 packages with the tag ORM available at NPM, deciding on one ORM may be a little bit overwhelming. Comparing three of the most-used ORMs will help you decide which is tool is best for your project. Whether it's Objection, Prisma, or Sequelize, read this post to determine your champion ORM!

What Are the Top 3 ORMs? 

Objection

Objection is a Node.JS ORM with over one hundred thousand weekly downloads. Built on the query builder Knex, Objection supports all databases supported by Knex. Objection’s main goal is to allow users to use the full power of SQL and your database’s storage engine. Objection may also be called a relational query builder.

Prisma

Prisma is an open-source Node.js and TypeScript ORM with half of a million weekly downloads. Prisma prounivides support for PostgreSQL, MySQL, SQL Server, SQLite, MongoDB, and CockroachDB. Centered around a GraphQL-like DSL schema, Prisma lets you cleanly define your database structure.

Sequelize

The most popular option, Sequelize is an open-source Node.js ORM with millions of weekly downloads. Sequelize provides support for Postgres, MySQL, MariaDB, SQLite, SQL Server, and more. Featuring solid transaction support, relations, eager- and lazy-loading, and read replication, Sequelize is a powerful tool. Sequelize also supports migrations and has a CLI tool for generating and seeding data.

Strengths of Each ORM

Objection

  • Very readable, intuitive syntax.

  • Allows chained operations

  • Great for migrations as it’s not tied to a changing model

  • Excellent support for RAW queries

  • Allows for a great deal of control over database operations

  • Makes joins straightforward

Prisma

  • Good CLI, capable of generating migrations and boilerplate files

  • Excellent documentation

  • Nice DSL for schema

  • Offers support for special keywords like “contains”, “includes”, “startsWith”

  • Support for RAW queries

  • Painless to import in existing project

  • Allows mapping column to custom properties name

  • Very popular

Sequelize

  • Strong CLI, capable of generating migrations, helping with seeds and boilerplate files

  • Getting started is straightforward

  • Support for RAW queries

  • Makes joins painless

  • Immensely popular


Weaknesses of Each ORM

Objection

  • Since models change over time, they shouldn’t be used for migrations

  • Requires a greater SQL knowledge in order to avoid non-performant queries

  • Less popular

  • Knex (its base) is too simple,  it's not even considered an ORM, but a query-builder

Prisma

  • It lacks a DBAL (Database Abstraction Layer)

  • Can be intimidating to beginners

Sequelize

  • Lack of high quality documentation

  • More advanced joining options are tricky

  • Little support for full text index searching


Code examples

Nothing helps you learn about ORM options more than code samples—especially when it comes to showing how these three compare. These code examples demonstrate main features of Objection, Prisma, and Sequelize. 

Objection

Configuration

Objection is unique because it needs to be used on top of Knex. The configuration file for an Objection + Knex project will be called knexfile.js, and it's going to look similar to this:

// .knexfile.js

export default {
  client: 'pg',
  useNullAsDefault: true,
  connection: process.env.DATABASE_CONNECTION_STRING ||
    'postgres://dbuser:dbpassword@localhost:5432/pet_store_objection',
  migrations: {
    tableName: 'knex_migrations',
    directory: 'migrations',
  },
  seeds: {
    directory: 'seeds',
  },
};

In this file you can set basic configurations like your connection, your folders and your database of choice.

Models

Though we are using Objection, the CLI is going to be Knex’s. Run npm install knex -g to globally install it.

Your model’s structure is going to be based on a JSON Schema, like this:

const { Model } = require('objection');

module.exports = class User extends Model {
  static get tableName() {
    return 'users';
  }

  static get jsonSchema() {
    return {
      type: 'object',
      required: ['id', 'firstName', 'lastName', 'email'],
      properties: {
        id: { type: 'string' },
        firstName: { type: 'string' },
        lastName: { type: 'string' },
        email: {
          type: 'string',
          minLength: 1,
          maxLength: 70,
        },
        created_at: {
          type: 'string', // datetimetz
        },
        updated_at: {
          type: 'string', // datetimetz
        },
      },
      additionalProperties: false,
    };
  }

  $beforeInsert() {
    this.created_at = new Date();
  }

  $beforeUpdate() {
    this.updated_at = new Date();
  }
};

This table will have the columns id, createdAt, and updateAt by default.

Migrations

The most common Knex commands for migration management are:

  • knex migrate:latest to apply the latest migration.

  • knex migrate:rollback to revert a migration.

  • knex migrate:make <migration_name> to generate a migration.

Example of migration file:

/**
 * @param { import("knex").Knex } knex
 * @returns { Promise<void> }
 */
exports.up = function (knex) {
  return knex.schema.createTable('users', (users) => {
    users.text('id').primary();
    users.text('firstName').notNullable();
    users.text('lastName').notNullable();
    users.text('email').notNullable();
    users.timestamps(true, true);
  });
};

/**
 * @param { import("knex").Knex } knex
 * @returns { Promise<void> }
 */
exports.down = function (knex) {
  return knex.schema.dropTableIfExists('users');
};

When you run your migrations, you’ll automatically get two tables with metadata about them. You can modify the name of those tables if you want.

Seeding

The two most useful commands when it comes to seeding are:

  • knex seed:make <seed_name> to generate a seed file.

  • knex seed:run to run your seeder.

Example of seeder file:

exports.seed = async function seed(knex) {
  const tableName = 'users';

  await knex(tableName).del();
  await knex(tableName).insert([
    {
      id: 1,
      firstName: 'Billie',
      lastName: 'Jean',
      email: 'girl@example.com',
    },
    {
      id: 2,
      firstName: 'Der',
      lastName: 'Erlkonig',
      email: 'poem@example.com',
    },
    {
      id: 3,
      firstName: 'Ophelia',
      lastName: 'Pseudochinensis',
      email: 'flower@example.com',
    },
  ]);
};

Simple queries

Simplified examples of common database operations

 
Finding all:
Model.query();
 
Create:
Model.query().insert({ firstName: "Britney", lastName: "Spears", email: "britney@pop.com" });
 
Passing where clauses to a find operation:
Model.query().where({ id: 1});
 
Find specific columns from table:
Model.query().select('id', 'lastName')
 
Update:
Model.query()
  .findById(1)
  .patch({ lastName: 'Jeans' });
 
Delete:
Model.query().deleteById(1);

Prisma

Configuration

Prisma’s configuration is centered around schema.prisma. This file is where you specify your data sources, your generators, and your data model definition (the app’s models and their relations).

// Default schema.prisma generated by the CLI

generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

Structure

Prisma has a great CLI. Run this to install Prisma and its client:

npm i prisma --save-dev && @prisma/client

Once Prisma is installed, run this to get your project started:

npx prisma init

The init command will automatically create a folder called Prisma, containing your schema.prisma. It will also create a .env file for you.

Models

In your schema.prisma, you can define your models like this:

// This is your Prisma schema file,
// learn more about it in the docs: <https://pris.ly/d/prisma-schema>

generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

model User {
  id       Int @id @default(autoincrement())
  firstName String
  lastName String  
  email String  
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
}

Define properties of your columns using attributes "@id".

After adjusting your schema.prisma, run npx prisma generate to generate your Prisma client. You’ll need to re-run this command each time you modify your schema.prisma.

Migrations

When it comes to migrations, Prisma is considerably different from Objection and Sequelize. Remember: Prisma is centered around the schema.prisma.

To migrate your database, you'll first modify your schema.prisma, then re-run the generate command, and lastly run npx prisma migrate dev --name init. The command will automatically generate and apply the SQL script for you, but you'll notice, though, that the .sql generated has no "down" method:

-- CreateTable
CREATE TABLE "User" (
  "id" SERIAL NOT NULL,
  "firstName" TEXT NOT NULL,
  "lastName" TEXT NOT NULL,
  "email" TEXT NOT NULL,
  "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
  "updatedAt" TIMESTAMP(3) NOT NULL,

  CONSTRAINT "User_pkey" PRIMARY KEY ("id")
);
  • To reset your database manual changes and make its structure match the migrations, run npx prisma migrate reset. Be careful with this command, it's supposed to be used only in development mode.

When you run your migrations, Prisma creates a folder called migrations for you and a table in your database called _prisma_migrations that stores data about your migrations.

Seeding

Prisma also differs a lot from the other two when it comes to seeding. First, you need to add a prisma attribute with a seeding property in your package.json:

"prisma": {
  "seed": "node ./prisma/seed.js"
}

Then, create a file called seed.js in the same path specified in your package.json and fill it with your seed data:

import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();

const load = async () => {
  try {
    const users = [
      {
        id: 1,
        firstName: 'Billie',
        lastName: 'Jean',
        email: 'girl@example.com',
      },
      {
        id: 2,
        firstName: 'Der',
        lastName: 'Erlkonig',
        email: 'poem@example.com',
      },
      {
        id: 3,
        firstName: 'Ophelia',
        lastName: 'Pseudochinensis',
        email: 'flower@example.com',
      },
    ]; 
    await prisma.user.createMany({ data: users });
  } catch (e) {
    console.error(e);
    process.exit(1);
  } finally {
    await prisma.$disconnect();
  }
};

load();

To run the seeds: npx prisma db seed

Simple queries

Simplified examples taken from https://sequelize.org/master/manual/model-querying-basics.html

 
Finding all:
prisma.model.findMany()

Create:
prisma.model.create({
  data: {
    id: 4,
    firstName: 'Britney',
    lastName: 'Spears',
    email: 'britney@pop.com',
  },
});
 
Passing where clauses to a find operation:
prisma.model.findUnique({
  where: { id: 1 }
});
 
Find specific columns from table:
prisma.model.findMany({
  select: {
    id: true,
    lastName: true,
  },
});
 
Update:
prisma.model.update({
  where: { id: 1 },
  data: {
    lastName: 'Jeans',
  },
});
 
Delete:
prisma.model.delete({
  where: { id: 1 }
});

Sequelize

Configuration

Sequelize we has a file called .sequelizerc, it is a special configuration that lets you specify some options that you normally would pass to the CLI, for example: env, url, and paths.

// .sequelizerc
require('babel-register');

const path = require('path');

module.exports = {
  config: path.resolve('config', 'config.json'),
  'models-path': path.resolve('models'),
  'seeders-path': path.resolve('seeders'),
  'migrations-path': path.resolve('migrations'),
};

Structure

Sequelize CLI can be used to create a great boilerplate structure. Run this to implement:

npm install --save-dev sequelize-cli && npx sequelize-cli init

Sequelize CLI will automatically create folders called models, config, seeders and migrations. All you need to do is fill it with relevant information.

Models

Use the command model:generate to create your models with the CLI. You can create a User's model and a migration that creates such a table by running:

npx sequelize-cli model:generate --name User --attributes firstName:string,lastName:string,email:string

This table will, by default, have the columns id, createdAt, and updateAt.

Migrations

When you create models with the CLI, it automatically creates the correct migration inside the “migration” folder.

  • npx sequelize-cli db:migrate to apply a migration.

  • npx sequelize-cli db:migrate:undo to revert a migration.

  • npx sequelize-cli migration:generate --name create-dogs-table to generate a migration.

Sequelize automatically creates a table called SequelizeMeta that stores an entry for the executed migrations. You can change this behavior in the configuration file, if needed.

Seeding

Following a similar pattern, you can easily generate seeds by running npx sequelize-cli seed:generate --name users.

This will generate a seeder boilerplate. In your example, it can be filled like this:

module.exports = {
  up: (queryInterface, Sequelize) => {
    return queryInterface.bulkInsert('Users', [
      {
        firstName: 'Billie',
        lastName: 'Jean',
        email: 'girl@example.com',
        createdAt: new Date(),
        updatedAt: new Date(),
      },
      {
        firstName: 'Der',
        lastName: 'Erlkonig',
        email: 'poem@example.com',
        createdAt: new Date(),
        updatedAt: new Date(),
      },
      {
        firstName: 'Ophelia',
        lastName: 'Pseudochinensis',
        email: 'flower@example.com',
        createdAt: new Date(),
        updatedAt: new Date(),
      },
    ]);
  },
  down: (queryInterface, Sequelize) => {
    return queryInterface.bulkDelete('Users', null, {});
  },
};

To run the seeds, run npx sequelize-cli db:seed:all.

To remove all previously seeded data from the database, run npx sequelize-cli db:seed:undo:all.

Simple queries

Simplified examples taken from https://sequelize.org/master/manual/model-querying-basics.html

 
Finding all:
Model.findAndCountAll({});
 
Create:
Model.create({ id: 4, firstName: "Britney", lastName: "Spears", email: "britney@pop.com" });
 
Passing where clauses to a find operation:
Model.findAll({
  where: {
    id: { [Op.eq]: 1 },
  },
});
 
Find specific columns from table:
Model.findAll({
  attributes: ["id", "lastName"],
});
 
Update:
Model.update(
  { lastName: 'Jeans' },
  { where: { id: 1 } }
);
 
Delete:
Model.destroy({
  where: { id: 1 }
});

Conclusion

Objection, Prisma, and Sequelize are all great ORM options with robust features. Now that you know the ORMs and their features, you can confidently choose the victor for your application. 

Still trying to decide?

Bitovi has expert backend web development consultants ready to help with your project! Schedule a free consultation to get started. 

 

Create better web applications. We’ll help. Let’s work together.